mirror of
https://github.com/Start9Labs/start-sdk.git
synced 2026-03-30 12:21:57 +00:00
chore: Add the tools for packaging npm
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1 +1,2 @@
|
|||||||
.vscode
|
.vscode
|
||||||
|
lib
|
||||||
16
Makefile
16
Makefile
@@ -1,5 +1,15 @@
|
|||||||
TEST_FILES := $(shell find ./**/*.ts)
|
TS_FILES := $(shell find ./**/*.ts )
|
||||||
|
version = $(shell git tag --sort=committerdate | tail -1)
|
||||||
test: $(TEST_FILES)
|
test: $(TS_FILES)
|
||||||
deno test test.ts
|
deno test test.ts
|
||||||
deno check mod.ts
|
deno check mod.ts
|
||||||
|
|
||||||
|
bundle: test fmt $(TS_FILES)
|
||||||
|
echo "Version: $(version)"
|
||||||
|
deno run --allow-net --allow-write --allow-env --allow-run --allow-read build.ts $(version)
|
||||||
|
|
||||||
|
fmt:
|
||||||
|
deno fmt
|
||||||
|
|
||||||
|
publish: bundle
|
||||||
|
cd lib && npm publish
|
||||||
30
build.ts
Normal file
30
build.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
// ex. scripts/build_npm.ts
|
||||||
|
import { build, emptyDir } from "https://deno.land/x/dnt@0.33.1/mod.ts";
|
||||||
|
|
||||||
|
await emptyDir("./lib");
|
||||||
|
await build({
|
||||||
|
entryPoints: ["./mod.ts"],
|
||||||
|
outDir: "./lib",
|
||||||
|
shims: {
|
||||||
|
// see JS docs for overview and more options
|
||||||
|
deno: true,
|
||||||
|
},
|
||||||
|
package: {
|
||||||
|
// package.json properties
|
||||||
|
name: "embassy-sdk-ts",
|
||||||
|
version: Deno.args[0],
|
||||||
|
description: "Sdk that is used by the embassy packages, and the OS.",
|
||||||
|
license: "MIT",
|
||||||
|
sideEffects: false,
|
||||||
|
repository: {
|
||||||
|
type: "git",
|
||||||
|
url: "git+https://github.com/Start9Labs/embassy-sdk-ts.git",
|
||||||
|
},
|
||||||
|
bugs: {
|
||||||
|
url: "https://github.com/Start9Labs/embassy-sdk-ts/issues",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// post build steps
|
||||||
|
Deno.copyFileSync("./README.md", "lib/README.md");
|
||||||
@@ -3,7 +3,7 @@ import { YAML } from "../dependencies.ts";
|
|||||||
import { matches } from "../dependencies.ts";
|
import { matches } from "../dependencies.ts";
|
||||||
import { ExpectedExports } from "../types.ts";
|
import { ExpectedExports } from "../types.ts";
|
||||||
import { ConfigSpec } from "../types/config-types.ts";
|
import { ConfigSpec } from "../types/config-types.ts";
|
||||||
import { typeFromProps, TypeFromProps } from "../utils/propertiesMatcher.ts";
|
import { TypeFromProps, typeFromProps } from "../utils/propertiesMatcher.ts";
|
||||||
|
|
||||||
const { any, string, dictionary } = matches;
|
const { any, string, dictionary } = matches;
|
||||||
|
|
||||||
@@ -18,8 +18,7 @@ const matchConfig = dictionary([string, any]);
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getConfig =
|
export const getConfig =
|
||||||
(spec: ConfigSpec): ExpectedExports.getConfig =>
|
(spec: ConfigSpec): ExpectedExports.getConfig => async (effects) => {
|
||||||
async (effects) => {
|
|
||||||
const config = await effects
|
const config = await effects
|
||||||
.readFile({
|
.readFile({
|
||||||
path: "start9/config.yaml",
|
path: "start9/config.yaml",
|
||||||
@@ -49,8 +48,11 @@ export const getConfig =
|
|||||||
* @returns A funnction for getConfig and the matcher for the spec sent in
|
* @returns A funnction for getConfig and the matcher for the spec sent in
|
||||||
*/
|
*/
|
||||||
export const getConfigAndMatcher = <Spec extends ConfigSpec>(
|
export const getConfigAndMatcher = <Spec extends ConfigSpec>(
|
||||||
spec: Config<Spec> | Spec
|
spec: Config<Spec> | Spec,
|
||||||
): [ExpectedExports.getConfig, matches.Parser<unknown, TypeFromProps<Spec>>] => {
|
): [
|
||||||
|
ExpectedExports.getConfig,
|
||||||
|
matches.Parser<unknown, TypeFromProps<Spec>>,
|
||||||
|
] => {
|
||||||
const specBuilt: Spec = spec instanceof Config ? spec.build() : spec;
|
const specBuilt: Spec = spec instanceof Config ? spec.build() : spec;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|||||||
@@ -17,11 +17,17 @@ export interface NoRepeat<version extends string, type extends "up" | "down"> {
|
|||||||
* @param noFail (optional, default:false) whether or not to fail the migration if fn throws an error
|
* @param noFail (optional, default:false) whether or not to fail the migration if fn throws an error
|
||||||
* @returns a migraion function
|
* @returns a migraion function
|
||||||
*/
|
*/
|
||||||
export function updateConfig<version extends string, type extends "up" | "down">(
|
export function updateConfig<
|
||||||
fn: (config: ConfigSpec, effects: T.Effects) => ConfigSpec | Promise<ConfigSpec>,
|
version extends string,
|
||||||
|
type extends "up" | "down",
|
||||||
|
>(
|
||||||
|
fn: (
|
||||||
|
config: ConfigSpec,
|
||||||
|
effects: T.Effects,
|
||||||
|
) => ConfigSpec | Promise<ConfigSpec>,
|
||||||
configured: boolean,
|
configured: boolean,
|
||||||
noRepeat?: NoRepeat<version, type>,
|
noRepeat?: NoRepeat<version, type>,
|
||||||
noFail = false
|
noFail = false,
|
||||||
): M.MigrationFn<version, type> {
|
): M.MigrationFn<version, type> {
|
||||||
return M.migrationFn(async (effects: T.Effects) => {
|
return M.migrationFn(async (effects: T.Effects) => {
|
||||||
await noRepeatGuard(effects, noRepeat, async () => {
|
await noRepeatGuard(effects, noRepeat, async () => {
|
||||||
@@ -43,15 +49,23 @@ export function updateConfig<version extends string, type extends "up" | "down">
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function noRepeatGuard<version extends string, type extends "up" | "down">(
|
export async function noRepeatGuard<
|
||||||
|
version extends string,
|
||||||
|
type extends "up" | "down",
|
||||||
|
>(
|
||||||
effects: T.Effects,
|
effects: T.Effects,
|
||||||
noRepeat: NoRepeat<version, type> | undefined,
|
noRepeat: NoRepeat<version, type> | undefined,
|
||||||
fn: () => Promise<void>
|
fn: () => Promise<void>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (!noRepeat) {
|
if (!noRepeat) {
|
||||||
return fn();
|
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" });
|
await effects.createDir({ path: "start9/migrations", volumeId: "main" });
|
||||||
}
|
}
|
||||||
const migrationPath = {
|
const migrationPath = {
|
||||||
@@ -74,9 +88,14 @@ export async function noRepeatGuard<version extends string, type extends "up" |
|
|||||||
export async function initNoRepeat<versions extends string>(
|
export async function initNoRepeat<versions extends string>(
|
||||||
effects: T.Effects,
|
effects: T.Effects,
|
||||||
migrations: M.MigrationMapping<versions>,
|
migrations: M.MigrationMapping<versions>,
|
||||||
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);
|
const starting = EmVer.parse(startingVersion);
|
||||||
await effects.createDir({ path: "start9/migrations", volumeId: "main" });
|
await effects.createDir({ path: "start9/migrations", volumeId: "main" });
|
||||||
for (const version in migrations) {
|
for (const version in migrations) {
|
||||||
@@ -94,11 +113,15 @@ export async function initNoRepeat<versions extends string>(
|
|||||||
|
|
||||||
export function fromMapping<versions extends string>(
|
export function fromMapping<versions extends string>(
|
||||||
migrations: M.MigrationMapping<versions>,
|
migrations: M.MigrationMapping<versions>,
|
||||||
currentVersion: string
|
currentVersion: string,
|
||||||
): T.ExpectedExports.migration {
|
): T.ExpectedExports.migration {
|
||||||
const inner = M.fromMapping(migrations, currentVersion);
|
const inner = M.fromMapping(migrations, currentVersion);
|
||||||
return async (effects: T.Effects, version: string, direction?: unknown) => {
|
return async (effects: T.Effects, version: string, direction?: unknown) => {
|
||||||
await initNoRepeat(effects, migrations, direction === "from" ? version : currentVersion);
|
await initNoRepeat(
|
||||||
|
effects,
|
||||||
|
migrations,
|
||||||
|
direction === "from" ? version : currentVersion,
|
||||||
|
);
|
||||||
return inner(effects, version, direction);
|
return inner(effects, version, direction);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,11 @@ import { ConfigSpec } from "../types/config-types.ts";
|
|||||||
* @param depends_on This would be the depends on for condition depends_on
|
* @param depends_on This would be the depends on for condition depends_on
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const setConfig = async (effects: Effects, newConfig: ConfigSpec, dependsOn: DependsOn = {}) => {
|
export const setConfig = async (
|
||||||
|
effects: Effects,
|
||||||
|
newConfig: ConfigSpec,
|
||||||
|
dependsOn: DependsOn = {},
|
||||||
|
) => {
|
||||||
await effects.createDir({
|
await effects.createDir({
|
||||||
path: "start9",
|
path: "start9",
|
||||||
volumeId: "main",
|
volumeId: "main",
|
||||||
|
|||||||
@@ -6,10 +6,16 @@ export class Config<A extends ConfigSpec> extends IBuilder<A> {
|
|||||||
static empty() {
|
static empty() {
|
||||||
return new Config({});
|
return new Config({});
|
||||||
}
|
}
|
||||||
static withValue<K extends string, B extends ValueSpec>(key: K, value: Value<B>) {
|
static withValue<K extends string, B extends ValueSpec>(
|
||||||
|
key: K,
|
||||||
|
value: Value<B>,
|
||||||
|
) {
|
||||||
return Config.empty().withValue(key, value);
|
return Config.empty().withValue(key, value);
|
||||||
}
|
}
|
||||||
static addValue<K extends string, B extends ValueSpec>(key: K, value: Value<B>) {
|
static addValue<K extends string, B extends ValueSpec>(
|
||||||
|
key: K,
|
||||||
|
value: Value<B>,
|
||||||
|
) {
|
||||||
return Config.empty().withValue(key, value);
|
return Config.empty().withValue(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,15 +29,19 @@ export class Config<A extends ConfigSpec> extends IBuilder<A> {
|
|||||||
return new Config(answer);
|
return new Config(answer);
|
||||||
}
|
}
|
||||||
withValue<K extends string, B extends ValueSpec>(key: K, value: Value<B>) {
|
withValue<K extends string, B extends ValueSpec>(key: K, value: Value<B>) {
|
||||||
return new Config({
|
return new Config(
|
||||||
...this.a,
|
{
|
||||||
[key]: value.build(),
|
...this.a,
|
||||||
} as A & { [key in K]: B });
|
[key]: value.build(),
|
||||||
|
} as A & { [key in K]: B },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
addValue<K extends string, B extends ValueSpec>(key: K, value: Value<B>) {
|
addValue<K extends string, B extends ValueSpec>(key: K, value: Value<B>) {
|
||||||
return new Config({
|
return new Config(
|
||||||
...this.a,
|
{
|
||||||
[key]: value.build(),
|
...this.a,
|
||||||
} as A & { [key in K]: B });
|
[key]: value.build(),
|
||||||
|
} as A & { [key in K]: B },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ test("Pointer", () => {
|
|||||||
"package-id": "bitcoind",
|
"package-id": "bitcoind",
|
||||||
interface: "peer",
|
interface: "peer",
|
||||||
warning: null,
|
warning: null,
|
||||||
})
|
}),
|
||||||
)
|
),
|
||||||
).build();
|
).build();
|
||||||
expect(JSON.stringify(bitcoinPropertiesBuilt)).toEqual(
|
expect(JSON.stringify(bitcoinPropertiesBuilt)).toEqual(
|
||||||
/*json*/ `{
|
/*json*/ `{
|
||||||
@@ -41,6 +41,6 @@ test("Pointer", () => {
|
|||||||
}}`
|
}}`
|
||||||
.replaceAll("\n", " ")
|
.replaceAll("\n", " ")
|
||||||
.replaceAll(/\s{2,}/g, "")
|
.replaceAll(/\s{2,}/g, "")
|
||||||
.replaceAll(": ", ":")
|
.replaceAll(": ", ":"),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,7 +4,12 @@ import { Default, NullableDefault, NumberSpec, StringSpec } from "./value.ts";
|
|||||||
import { Description } from "./value.ts";
|
import { Description } from "./value.ts";
|
||||||
import * as T from "../types.ts";
|
import * as T from "../types.ts";
|
||||||
import { Variants } from "./variants.ts";
|
import { Variants } from "./variants.ts";
|
||||||
import { ConfigSpec, UniqueBy, ValueSpecList, ValueSpecListOf } from "../types/config-types.ts";
|
import {
|
||||||
|
ConfigSpec,
|
||||||
|
UniqueBy,
|
||||||
|
ValueSpecList,
|
||||||
|
ValueSpecListOf,
|
||||||
|
} from "../types/config-types.ts";
|
||||||
|
|
||||||
export class List<A extends ValueSpecList> extends IBuilder<A> {
|
export class List<A extends ValueSpecList> extends IBuilder<A> {
|
||||||
// // deno-lint-ignore ban-types
|
// // deno-lint-ignore ban-types
|
||||||
@@ -16,14 +21,24 @@ export class List<A extends ValueSpecList> extends IBuilder<A> {
|
|||||||
// });
|
// });
|
||||||
// }
|
// }
|
||||||
|
|
||||||
static string<A extends Description & Default<string[]> & { range: string; spec: StringSpec }>(a: A) {
|
static string<
|
||||||
|
A extends Description & Default<string[]> & {
|
||||||
|
range: string;
|
||||||
|
spec: StringSpec;
|
||||||
|
},
|
||||||
|
>(a: A) {
|
||||||
return new List({
|
return new List({
|
||||||
type: "list" as const,
|
type: "list" as const,
|
||||||
subtype: "string" as const,
|
subtype: "string" as const,
|
||||||
...a,
|
...a,
|
||||||
} as ValueSpecListOf<"string">);
|
} as ValueSpecListOf<"string">);
|
||||||
}
|
}
|
||||||
static number<A extends Description & Default<number[]> & { range: string; spec: NumberSpec }>(a: A) {
|
static number<
|
||||||
|
A extends Description & Default<number[]> & {
|
||||||
|
range: string;
|
||||||
|
spec: NumberSpec;
|
||||||
|
},
|
||||||
|
>(a: A) {
|
||||||
return new List({
|
return new List({
|
||||||
type: "list" as const,
|
type: "list" as const,
|
||||||
subtype: "number" as const,
|
subtype: "number" as const,
|
||||||
@@ -31,8 +46,10 @@ export class List<A extends ValueSpecList> extends IBuilder<A> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
static enum<
|
static enum<
|
||||||
A extends Description &
|
A extends
|
||||||
Default<string[]> & {
|
& Description
|
||||||
|
& Default<string[]>
|
||||||
|
& {
|
||||||
range: string;
|
range: string;
|
||||||
spec: {
|
spec: {
|
||||||
values: string[];
|
values: string[];
|
||||||
@@ -40,7 +57,7 @@ export class List<A extends ValueSpecList> extends IBuilder<A> {
|
|||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
>(a: A) {
|
>(a: A) {
|
||||||
return new List({
|
return new List({
|
||||||
type: "list" as const,
|
type: "list" as const,
|
||||||
@@ -49,19 +66,23 @@ export class List<A extends ValueSpecList> extends IBuilder<A> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
static objectV<
|
static objectV<
|
||||||
A extends Description &
|
A extends
|
||||||
Default<Record<string, unknown>[]> & {
|
& Description
|
||||||
|
& Default<Record<string, unknown>[]>
|
||||||
|
& {
|
||||||
range: string;
|
range: string;
|
||||||
spec: {
|
spec: {
|
||||||
spec: Config<ConfigSpec>;
|
spec: Config<ConfigSpec>;
|
||||||
"display-as": null | string;
|
"display-as": null | string;
|
||||||
"unique-by": null | UniqueBy;
|
"unique-by": null | UniqueBy;
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
>(a: A) {
|
>(a: A) {
|
||||||
const { spec: previousSpec, ...rest } = a;
|
const { spec: previousSpec, ...rest } = a;
|
||||||
const { spec: previousSpecSpec, ...restSpec } = previousSpec;
|
const { spec: previousSpecSpec, ...restSpec } = previousSpec;
|
||||||
const specSpec = previousSpecSpec.build() as BuilderExtract<A["spec"]["spec"]>;
|
const specSpec = previousSpecSpec.build() as BuilderExtract<
|
||||||
|
A["spec"]["spec"]
|
||||||
|
>;
|
||||||
const spec = {
|
const spec = {
|
||||||
...restSpec,
|
...restSpec,
|
||||||
spec: specSpec,
|
spec: specSpec,
|
||||||
@@ -77,8 +98,10 @@ export class List<A extends ValueSpecList> extends IBuilder<A> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
static union<
|
static union<
|
||||||
A extends Description &
|
A extends
|
||||||
Default<string[]> & {
|
& Description
|
||||||
|
& Default<string[]>
|
||||||
|
& {
|
||||||
range: string;
|
range: string;
|
||||||
spec: {
|
spec: {
|
||||||
tag: {
|
tag: {
|
||||||
@@ -94,11 +117,13 @@ export class List<A extends ValueSpecList> extends IBuilder<A> {
|
|||||||
"unique-by": UniqueBy;
|
"unique-by": UniqueBy;
|
||||||
default: string;
|
default: string;
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
>(a: A) {
|
>(a: A) {
|
||||||
const { spec: previousSpec, ...rest } = a;
|
const { spec: previousSpec, ...rest } = a;
|
||||||
const { variants: previousVariants, ...restSpec } = previousSpec;
|
const { variants: previousVariants, ...restSpec } = previousSpec;
|
||||||
const variants = previousVariants.build() as BuilderExtract<A["spec"]["variants"]>;
|
const variants = previousVariants.build() as BuilderExtract<
|
||||||
|
A["spec"]["variants"]
|
||||||
|
>;
|
||||||
const spec = {
|
const spec = {
|
||||||
...restSpec,
|
...restSpec,
|
||||||
variants,
|
variants,
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ import { IBuilder } from "./builder.ts";
|
|||||||
import { Description } from "./value.ts";
|
import { Description } from "./value.ts";
|
||||||
|
|
||||||
export class Pointer<A extends ValueSpec> extends IBuilder<A> {
|
export class Pointer<A extends ValueSpec> extends IBuilder<A> {
|
||||||
static packageTorKey<A extends Description & { "package-id": string; interface: string }>(a: A) {
|
static packageTorKey<
|
||||||
|
A extends Description & { "package-id": string; interface: string },
|
||||||
|
>(a: A) {
|
||||||
return new Pointer({
|
return new Pointer({
|
||||||
type: "pointer" as const,
|
type: "pointer" as const,
|
||||||
subtype: "package" as const,
|
subtype: "package" as const,
|
||||||
@@ -11,7 +13,9 @@ export class Pointer<A extends ValueSpec> extends IBuilder<A> {
|
|||||||
...a,
|
...a,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
static packageTorAddress<A extends Description & { "package-id": string; interface: string }>(a: A) {
|
static packageTorAddress<
|
||||||
|
A extends Description & { "package-id": string; interface: string },
|
||||||
|
>(a: A) {
|
||||||
return new Pointer({
|
return new Pointer({
|
||||||
type: "pointer" as const,
|
type: "pointer" as const,
|
||||||
subtype: "package" as const,
|
subtype: "package" as const,
|
||||||
@@ -19,7 +23,9 @@ export class Pointer<A extends ValueSpec> extends IBuilder<A> {
|
|||||||
...a,
|
...a,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
static packageLanAddress<A extends Description & { "package-id": string; interface: string }>(a: A) {
|
static packageLanAddress<
|
||||||
|
A extends Description & { "package-id": string; interface: string },
|
||||||
|
>(a: A) {
|
||||||
return new Pointer({
|
return new Pointer({
|
||||||
type: "pointer" as const,
|
type: "pointer" as const,
|
||||||
subtype: "package" as const,
|
subtype: "package" as const,
|
||||||
@@ -28,7 +34,12 @@ export class Pointer<A extends ValueSpec> extends IBuilder<A> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
static packageConfig<
|
static packageConfig<
|
||||||
A extends Description & { "package-id": string; selector: string; multi: boolean; interface: string }
|
A extends Description & {
|
||||||
|
"package-id": string;
|
||||||
|
selector: string;
|
||||||
|
multi: boolean;
|
||||||
|
interface: string;
|
||||||
|
},
|
||||||
>(a: A) {
|
>(a: A) {
|
||||||
return new Pointer({
|
return new Pointer({
|
||||||
type: "pointer" as const,
|
type: "pointer" as const,
|
||||||
@@ -37,8 +48,15 @@ export class Pointer<A extends ValueSpec> extends IBuilder<A> {
|
|||||||
...a,
|
...a,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
static system<A extends Description & { "package-id": string; selector: string; multi: boolean; interface: string }>(
|
static system<
|
||||||
a: A
|
A extends Description & {
|
||||||
|
"package-id": string;
|
||||||
|
selector: string;
|
||||||
|
multi: boolean;
|
||||||
|
interface: string;
|
||||||
|
},
|
||||||
|
>(
|
||||||
|
a: A,
|
||||||
) {
|
) {
|
||||||
return new Pointer({
|
return new Pointer({
|
||||||
type: "pointer" as const,
|
type: "pointer" as const,
|
||||||
|
|||||||
@@ -9,16 +9,15 @@ import {
|
|||||||
ValueSpec,
|
ValueSpec,
|
||||||
ValueSpecList,
|
ValueSpecList,
|
||||||
ValueSpecNumber,
|
ValueSpecNumber,
|
||||||
ValueSpecObject,
|
|
||||||
ValueSpecString,
|
ValueSpecString,
|
||||||
} from "../types/config-types.ts";
|
} from "../types/config-types.ts";
|
||||||
|
|
||||||
export type DefaultString =
|
export type DefaultString =
|
||||||
| string
|
| string
|
||||||
| {
|
| {
|
||||||
charset: string | null | undefined;
|
charset: string | null | undefined;
|
||||||
len: number;
|
len: number;
|
||||||
};
|
};
|
||||||
export type Description = {
|
export type Description = {
|
||||||
name: string;
|
name: string;
|
||||||
description: string | null;
|
description: string | null;
|
||||||
@@ -31,18 +30,20 @@ export type NullableDefault<A> = {
|
|||||||
default?: A;
|
default?: A;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type StringSpec = {
|
export type StringSpec =
|
||||||
copyable: boolean | null;
|
& {
|
||||||
masked: boolean | null;
|
copyable: boolean | null;
|
||||||
placeholder: string | null;
|
masked: boolean | null;
|
||||||
} & (
|
placeholder: string | null;
|
||||||
| {
|
}
|
||||||
|
& (
|
||||||
|
| {
|
||||||
pattern: string;
|
pattern: string;
|
||||||
"pattern-description": string;
|
"pattern-description": string;
|
||||||
}
|
}
|
||||||
// deno-lint-ignore ban-types
|
// deno-lint-ignore ban-types
|
||||||
| {}
|
| {}
|
||||||
);
|
);
|
||||||
export type NumberSpec = {
|
export type NumberSpec = {
|
||||||
range: string;
|
range: string;
|
||||||
integral: boolean;
|
integral: boolean;
|
||||||
@@ -60,21 +61,34 @@ export class Value<A extends ValueSpec> extends IBuilder<A> {
|
|||||||
...a,
|
...a,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
static string<A extends Description & NullableDefault<DefaultString> & Nullable & StringSpec>(a: A) {
|
static string<
|
||||||
|
A extends
|
||||||
|
& Description
|
||||||
|
& NullableDefault<DefaultString>
|
||||||
|
& Nullable
|
||||||
|
& StringSpec,
|
||||||
|
>(a: A) {
|
||||||
return new Value({
|
return new Value({
|
||||||
type: "string" as const,
|
type: "string" as const,
|
||||||
...a,
|
...a,
|
||||||
} as ValueSpecString);
|
} as ValueSpecString);
|
||||||
}
|
}
|
||||||
static number<A extends Description & NullableDefault<number> & Nullable & NumberSpec>(a: A) {
|
static number<
|
||||||
|
A extends Description & NullableDefault<number> & Nullable & NumberSpec,
|
||||||
|
>(a: A) {
|
||||||
return new Value({
|
return new Value({
|
||||||
type: "number" as const,
|
type: "number" as const,
|
||||||
...a,
|
...a,
|
||||||
} as ValueSpecNumber);
|
} as ValueSpecNumber);
|
||||||
}
|
}
|
||||||
static enum<
|
static enum<
|
||||||
A extends Description &
|
A extends
|
||||||
Default<string> & { values: readonly string[] | string[]; "value-names": Record<string, string> }
|
& Description
|
||||||
|
& Default<string>
|
||||||
|
& {
|
||||||
|
values: readonly string[] | string[];
|
||||||
|
"value-names": Record<string, string>;
|
||||||
|
},
|
||||||
>(a: A) {
|
>(a: A) {
|
||||||
return new Value({
|
return new Value({
|
||||||
type: "enum" as const,
|
type: "enum" as const,
|
||||||
@@ -91,7 +105,7 @@ export class Value<A extends ValueSpec> extends IBuilder<A> {
|
|||||||
"unique-by": null | string;
|
"unique-by": null | string;
|
||||||
spec: Config<ConfigSpec>;
|
spec: Config<ConfigSpec>;
|
||||||
"value-names": Record<string, string>;
|
"value-names": Record<string, string>;
|
||||||
}
|
},
|
||||||
>(a: A) {
|
>(a: A) {
|
||||||
const { spec: previousSpec, ...rest } = a;
|
const { spec: previousSpec, ...rest } = a;
|
||||||
const spec = previousSpec.build() as BuilderExtract<A["spec"]>;
|
const spec = previousSpec.build() as BuilderExtract<A["spec"]>;
|
||||||
@@ -102,8 +116,10 @@ export class Value<A extends ValueSpec> extends IBuilder<A> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
static union<
|
static union<
|
||||||
A extends Description &
|
A extends
|
||||||
Default<string> & {
|
& Description
|
||||||
|
& Default<string>
|
||||||
|
& {
|
||||||
tag: {
|
tag: {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -116,7 +132,7 @@ export class Value<A extends ValueSpec> extends IBuilder<A> {
|
|||||||
variants: Variants<{ [key: string]: ConfigSpec }>;
|
variants: Variants<{ [key: string]: ConfigSpec }>;
|
||||||
"display-as": string | null;
|
"display-as": string | null;
|
||||||
"unique-by": UniqueBy;
|
"unique-by": UniqueBy;
|
||||||
}
|
},
|
||||||
>(a: A) {
|
>(a: A) {
|
||||||
const { variants: previousVariants, ...rest } = a;
|
const { variants: previousVariants, ...rest } = a;
|
||||||
const variants = previousVariants.build() as BuilderExtract<A["variants"]>;
|
const variants = previousVariants.build() as BuilderExtract<A["variants"]>;
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ import { ConfigSpec } from "../types/config-types.ts";
|
|||||||
import { BuilderExtract, IBuilder } from "./builder.ts";
|
import { BuilderExtract, IBuilder } from "./builder.ts";
|
||||||
import { Config } from "./mod.ts";
|
import { Config } from "./mod.ts";
|
||||||
|
|
||||||
export class Variants<A extends { [key: string]: ConfigSpec }> extends IBuilder<A> {
|
export class Variants<A extends { [key: string]: ConfigSpec }>
|
||||||
|
extends IBuilder<A> {
|
||||||
static of<
|
static of<
|
||||||
A extends {
|
A extends {
|
||||||
[key: string]: Config<ConfigSpec>;
|
[key: string]: Config<ConfigSpec>;
|
||||||
}
|
},
|
||||||
>(a: A) {
|
>(a: A) {
|
||||||
// deno-lint-ignore no-explicit-any
|
// deno-lint-ignore no-explicit-any
|
||||||
const variants: { [K in keyof A]: BuilderExtract<A[K]> } = {} as any;
|
const variants: { [K in keyof A]: BuilderExtract<A[K]> } = {} as any;
|
||||||
@@ -20,14 +21,22 @@ export class Variants<A extends { [key: string]: ConfigSpec }> extends IBuilder<
|
|||||||
static empty() {
|
static empty() {
|
||||||
return Variants.of({});
|
return Variants.of({});
|
||||||
}
|
}
|
||||||
static withVariant<K extends string, B extends ConfigSpec>(key: K, value: Config<B>) {
|
static withVariant<K extends string, B extends ConfigSpec>(
|
||||||
|
key: K,
|
||||||
|
value: Config<B>,
|
||||||
|
) {
|
||||||
return Variants.empty().withVariant(key, value);
|
return Variants.empty().withVariant(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
withVariant<K extends string, B extends ConfigSpec>(key: K, value: Config<B>) {
|
withVariant<K extends string, B extends ConfigSpec>(
|
||||||
return new Variants({
|
key: K,
|
||||||
...this.a,
|
value: Config<B>,
|
||||||
[key]: value.build(),
|
) {
|
||||||
} as A & { [key in K]: B });
|
return new Variants(
|
||||||
|
{
|
||||||
|
...this.a,
|
||||||
|
[key]: value.build(),
|
||||||
|
} as A & { [key in K]: B },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
61
types.ts
61
types.ts
@@ -4,7 +4,10 @@ import { ConfigSpec } from "./types/config-types.ts";
|
|||||||
// deno-lint-ignore no-namespace
|
// deno-lint-ignore no-namespace
|
||||||
export namespace ExpectedExports {
|
export namespace ExpectedExports {
|
||||||
/** 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. */
|
/** 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: ConfigSpec) => Promise<ResultType<SetResult>>;
|
export type setConfig = (
|
||||||
|
effects: Effects,
|
||||||
|
input: ConfigSpec,
|
||||||
|
) => Promise<ResultType<SetResult>>;
|
||||||
/** Get configuration returns a shape that describes the format that the embassy ui will generate, and later send to the set config */
|
/** 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<ResultType<ConfigRes>>;
|
export type getConfig = (effects: Effects) => Promise<ResultType<ConfigRes>>;
|
||||||
/** These are how we make sure the our dependency configurations are valid and if not how to fix them. */
|
/** These are how we make sure the our dependency configurations are valid and if not how to fix them. */
|
||||||
@@ -12,23 +15,34 @@ export namespace ExpectedExports {
|
|||||||
/** For backing up service data though the embassyOS UI */
|
/** For backing up service data though the embassyOS UI */
|
||||||
export type createBackup = (effects: Effects) => Promise<ResultType<unknown>>;
|
export type createBackup = (effects: Effects) => Promise<ResultType<unknown>>;
|
||||||
/** 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. */
|
/** 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<ResultType<unknown>>;
|
export type restoreBackup = (
|
||||||
|
effects: Effects,
|
||||||
|
) => Promise<ResultType<unknown>>;
|
||||||
/** Properties are used to get values from the docker, like a username + password, what ports we are hosting from */
|
/** 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<ResultType<Properties>>;
|
export type properties = (
|
||||||
|
effects: Effects,
|
||||||
|
) => Promise<ResultType<Properties>>;
|
||||||
|
|
||||||
/** Health checks are used to determine if the service is working properly after starting
|
/** Health checks are used to determine if the service is working properly after starting
|
||||||
* A good use case is if we are using a web server, seeing if we can get to the web server.
|
* A good use case is if we are using a web server, seeing if we can get to the web server.
|
||||||
*/
|
*/
|
||||||
export type health = {
|
export type health = {
|
||||||
/** Should be the health check id */
|
/** Should be the health check id */
|
||||||
[id: string]: (effects: Effects, dateMs: number) => Promise<ResultType<unknown>>;
|
[id: string]: (
|
||||||
|
effects: Effects,
|
||||||
|
dateMs: number,
|
||||||
|
) => Promise<ResultType<unknown>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Migrations are used when we are changing versions when updating/ downgrading.
|
* Migrations are used when we are changing versions when updating/ downgrading.
|
||||||
* There are times that we need to move files around, and do other operations during a migration.
|
* There are times that we need to move files around, and do other operations during a migration.
|
||||||
*/
|
*/
|
||||||
export type migration = (effects: Effects, version: string, ...args: unknown[]) => Promise<ResultType<MigrationRes>>;
|
export type migration = (
|
||||||
|
effects: Effects,
|
||||||
|
version: string,
|
||||||
|
...args: unknown[]
|
||||||
|
) => Promise<ResultType<MigrationRes>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Actions are used so we can effect the service, like deleting a directory.
|
* Actions are used so we can effect the service, like deleting a directory.
|
||||||
@@ -36,7 +50,10 @@ export namespace ExpectedExports {
|
|||||||
* service starting, and that file would indicate that it would rescan all the data.
|
* service starting, and that file would indicate that it would rescan all the data.
|
||||||
*/
|
*/
|
||||||
export type action = {
|
export type action = {
|
||||||
[id: string]: (effects: Effects, config?: ConfigSpec) => Promise<ResultType<ActionResult>>;
|
[id: string]: (
|
||||||
|
effects: Effects,
|
||||||
|
config?: ConfigSpec,
|
||||||
|
) => Promise<ResultType<ActionResult>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -55,7 +72,9 @@ export type ConfigRes = {
|
|||||||
/** Used to reach out from the pure js runtime */
|
/** Used to reach out from the pure js runtime */
|
||||||
export type Effects = {
|
export type Effects = {
|
||||||
/** Usable when not sandboxed */
|
/** Usable when not sandboxed */
|
||||||
writeFile(input: { path: string; volumeId: string; toWrite: string }): Promise<void>;
|
writeFile(
|
||||||
|
input: { path: string; volumeId: string; toWrite: string },
|
||||||
|
): Promise<void>;
|
||||||
readFile(input: { volumeId: string; path: string }): Promise<string>;
|
readFile(input: { volumeId: string; path: string }): Promise<string>;
|
||||||
metadata(input: { volumeId: string; path: string }): Promise<Metadata>;
|
metadata(input: { volumeId: string; path: string }): Promise<Metadata>;
|
||||||
/** Create a directory. Usable when not sandboxed */
|
/** Create a directory. Usable when not sandboxed */
|
||||||
@@ -67,12 +86,18 @@ export type Effects = {
|
|||||||
removeFile(input: { volumeId: string; path: string }): Promise<void>;
|
removeFile(input: { volumeId: string; path: string }): Promise<void>;
|
||||||
|
|
||||||
/** Write a json file into an object. Usable when not sandboxed */
|
/** Write a json file into an object. Usable when not sandboxed */
|
||||||
writeJsonFile(input: { volumeId: string; path: string; toWrite: Record<string, unknown> }): Promise<void>;
|
writeJsonFile(
|
||||||
|
input: { volumeId: string; path: string; toWrite: Record<string, unknown> },
|
||||||
|
): Promise<void>;
|
||||||
|
|
||||||
/** Read a json file into an object */
|
/** Read a json file into an object */
|
||||||
readJsonFile(input: { volumeId: string; path: string }): Promise<Record<string, unknown>>;
|
readJsonFile(
|
||||||
|
input: { volumeId: string; path: string },
|
||||||
|
): Promise<Record<string, unknown>>;
|
||||||
|
|
||||||
runCommand(input: { command: string; args?: string[]; timeoutMillis?: number }): Promise<ResultType<string>>;
|
runCommand(
|
||||||
|
input: { command: string; args?: string[]; timeoutMillis?: number },
|
||||||
|
): Promise<ResultType<string>>;
|
||||||
runDaemon(input: { command: string; args?: string[] }): {
|
runDaemon(input: { command: string; args?: string[] }): {
|
||||||
wait(): Promise<ResultType<string>>;
|
wait(): Promise<ResultType<string>>;
|
||||||
term(): Promise<void>;
|
term(): Promise<void>;
|
||||||
@@ -102,7 +127,7 @@ export type Effects = {
|
|||||||
method?: "GET" | "POST" | "PUT" | "DELETE" | "HEAD" | "PATCH";
|
method?: "GET" | "POST" | "PUT" | "DELETE" | "HEAD" | "PATCH";
|
||||||
headers?: Record<string, string>;
|
headers?: Record<string, string>;
|
||||||
body?: string;
|
body?: string;
|
||||||
}
|
},
|
||||||
): Promise<{
|
): Promise<{
|
||||||
method: string;
|
method: string;
|
||||||
ok: boolean;
|
ok: boolean;
|
||||||
@@ -208,8 +233,8 @@ export type DependsOn = {
|
|||||||
export type KnownError =
|
export type KnownError =
|
||||||
| { error: string }
|
| { error: string }
|
||||||
| {
|
| {
|
||||||
"error-code": [number, string] | readonly [number, string];
|
"error-code": [number, string] | readonly [number, string];
|
||||||
};
|
};
|
||||||
export type ResultType<T> = KnownError | { result: T };
|
export type ResultType<T> = KnownError | { result: T };
|
||||||
|
|
||||||
export type PackagePropertiesV2 = {
|
export type PackagePropertiesV2 = {
|
||||||
@@ -241,8 +266,14 @@ export type Dependencies = {
|
|||||||
/** Id is the id of the package, should be the same as the manifest */
|
/** Id is the id of the package, should be the same as the manifest */
|
||||||
[id: string]: {
|
[id: string]: {
|
||||||
/** Checks are called to make sure that our dependency is in the correct shape. If a known error is returned we know that the dependency needs modification */
|
/** Checks are called to make sure that our dependency is in the correct shape. If a known error is returned we know that the dependency needs modification */
|
||||||
check(effects: Effects, input: ConfigSpec): Promise<ResultType<void | null>>;
|
check(
|
||||||
|
effects: Effects,
|
||||||
|
input: ConfigSpec,
|
||||||
|
): Promise<ResultType<void | null>>;
|
||||||
/** This is called after we know that the dependency package needs a new configuration, this would be a transform for defaults */
|
/** This is called after we know that the dependency package needs a new configuration, this would be a transform for defaults */
|
||||||
autoConfigure(effects: Effects, input: ConfigSpec): Promise<ResultType<ConfigSpec>>;
|
autoConfigure(
|
||||||
|
effects: Effects,
|
||||||
|
input: ConfigSpec,
|
||||||
|
): Promise<ResultType<ConfigSpec>>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,26 +1,27 @@
|
|||||||
// deno-lint-ignore-file ban-types
|
// deno-lint-ignore-file ban-types
|
||||||
export type ConfigSpec = Record<string, ValueSpec>;
|
export type ConfigSpec = Record<string, ValueSpec>;
|
||||||
|
|
||||||
export type ValueType = "string" | "number" | "boolean" | "enum" | "list" | "object" | "pointer" | "union";
|
export type ValueType =
|
||||||
|
| "string"
|
||||||
|
| "number"
|
||||||
|
| "boolean"
|
||||||
|
| "enum"
|
||||||
|
| "list"
|
||||||
|
| "object"
|
||||||
|
| "pointer"
|
||||||
|
| "union";
|
||||||
export type ValueSpec = ValueSpecOf<ValueType>;
|
export type ValueSpec = ValueSpecOf<ValueType>;
|
||||||
|
|
||||||
// core spec types. These types provide the metadata for performing validations
|
// core spec types. These types provide the metadata for performing validations
|
||||||
export type ValueSpecOf<T extends ValueType> = T extends "string"
|
export type ValueSpecOf<T extends ValueType> = T extends "string"
|
||||||
? ValueSpecString
|
? ValueSpecString
|
||||||
: T extends "number"
|
: T extends "number" ? ValueSpecNumber
|
||||||
? ValueSpecNumber
|
: T extends "boolean" ? ValueSpecBoolean
|
||||||
: T extends "boolean"
|
: T extends "enum" ? ValueSpecEnum
|
||||||
? ValueSpecBoolean
|
: T extends "list" ? ValueSpecList
|
||||||
: T extends "enum"
|
: T extends "object" ? ValueSpecObject
|
||||||
? ValueSpecEnum
|
: T extends "pointer" ? ValueSpecPointer
|
||||||
: T extends "list"
|
: T extends "union" ? ValueSpecUnion
|
||||||
? ValueSpecList
|
|
||||||
: T extends "object"
|
|
||||||
? ValueSpecObject
|
|
||||||
: T extends "pointer"
|
|
||||||
? ValueSpecPointer
|
|
||||||
: T extends "union"
|
|
||||||
? ValueSpecUnion
|
|
||||||
: never;
|
: never;
|
||||||
|
|
||||||
export interface ValueSpecString extends ListValueSpecString, WithStandalone {
|
export interface ValueSpecString extends ListValueSpecString, WithStandalone {
|
||||||
@@ -75,24 +76,26 @@ export interface WithStandalone {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// no lists of booleans, lists, pointers
|
// no lists of booleans, lists, pointers
|
||||||
export type ListValueSpecType = "string" | "number" | "enum" | "object" | "union";
|
export type ListValueSpecType =
|
||||||
|
| "string"
|
||||||
|
| "number"
|
||||||
|
| "enum"
|
||||||
|
| "object"
|
||||||
|
| "union";
|
||||||
|
|
||||||
// represents a spec for the values of a list
|
// represents a spec for the values of a list
|
||||||
export type ListValueSpecOf<T extends ListValueSpecType> = T extends "string"
|
export type ListValueSpecOf<T extends ListValueSpecType> = T extends "string"
|
||||||
? ListValueSpecString
|
? ListValueSpecString
|
||||||
: T extends "number"
|
: T extends "number" ? ListValueSpecNumber
|
||||||
? ListValueSpecNumber
|
: T extends "enum" ? ListValueSpecEnum
|
||||||
: T extends "enum"
|
: T extends "object" ? ListValueSpecObject
|
||||||
? ListValueSpecEnum
|
: T extends "union" ? ListValueSpecUnion
|
||||||
: T extends "object"
|
|
||||||
? ListValueSpecObject
|
|
||||||
: T extends "union"
|
|
||||||
? ListValueSpecUnion
|
|
||||||
: never;
|
: never;
|
||||||
|
|
||||||
// represents a spec for a list
|
// represents a spec for a list
|
||||||
export type ValueSpecList = ValueSpecListOf<ListValueSpecType>;
|
export type ValueSpecList = ValueSpecListOf<ListValueSpecType>;
|
||||||
export interface ValueSpecListOf<T extends ListValueSpecType> extends WithStandalone {
|
export interface ValueSpecListOf<T extends ListValueSpecType>
|
||||||
|
extends WithStandalone {
|
||||||
type: "list";
|
type: "list";
|
||||||
subtype: T;
|
subtype: T;
|
||||||
spec: ListValueSpecOf<T>;
|
spec: ListValueSpecOf<T>;
|
||||||
@@ -109,7 +112,10 @@ export interface ValueSpecListOf<T extends ListValueSpecType> extends WithStanda
|
|||||||
}
|
}
|
||||||
|
|
||||||
// sometimes the type checker needs just a little bit of help
|
// sometimes the type checker needs just a little bit of help
|
||||||
export function isValueSpecListOf<S extends ListValueSpecType>(t: ValueSpecList, s: S): t is ValueSpecListOf<S> {
|
export function isValueSpecListOf<S extends ListValueSpecType>(
|
||||||
|
t: ValueSpecList,
|
||||||
|
s: S,
|
||||||
|
): t is ValueSpecListOf<S> {
|
||||||
return t.subtype === s;
|
return t.subtype === s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
7
util.ts
7
util.ts
@@ -13,10 +13,13 @@ export function unwrapResultType<T>(res: T.ResultType<T>): T {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Used to check if the file exists before hand */
|
/** Used to check if the file exists before hand */
|
||||||
export const exists = (effects: T.Effects, props: { path: string; volumeId: string }) =>
|
export const exists = (
|
||||||
|
effects: T.Effects,
|
||||||
|
props: { path: string; volumeId: string },
|
||||||
|
) =>
|
||||||
effects.metadata(props).then(
|
effects.metadata(props).then(
|
||||||
(_) => true,
|
(_) => true,
|
||||||
(_) => false
|
(_) => false,
|
||||||
);
|
);
|
||||||
|
|
||||||
export const errorCode = (code: number, error: string) => ({
|
export const errorCode = (code: number, error: string) => ({
|
||||||
|
|||||||
@@ -76,14 +76,16 @@ const bitcoinProperties = {
|
|||||||
default: Array<string>(),
|
default: Array<string>(),
|
||||||
spec: {
|
spec: {
|
||||||
pattern: "^[a-zA-Z0-9_-]+:([0-9a-fA-F]{2})+\\$([0-9a-fA-F]{2})+$",
|
pattern: "^[a-zA-Z0-9_-]+:([0-9a-fA-F]{2})+\\$([0-9a-fA-F]{2})+$",
|
||||||
"pattern-description": 'Each item must be of the form "<USERNAME>:<SALT>$<HASH>".',
|
"pattern-description":
|
||||||
|
'Each item must be of the form "<USERNAME>:<SALT>$<HASH>".',
|
||||||
masked: false,
|
masked: false,
|
||||||
},
|
},
|
||||||
range: "[0,*)",
|
range: "[0,*)",
|
||||||
},
|
},
|
||||||
serialversion: {
|
serialversion: {
|
||||||
name: "Serialization Version",
|
name: "Serialization Version",
|
||||||
description: "Return raw transaction or block hex with Segwit or non-SegWit serialization.",
|
description:
|
||||||
|
"Return raw transaction or block hex with Segwit or non-SegWit serialization.",
|
||||||
type: "enum",
|
type: "enum",
|
||||||
values: ["non-segwit", "segwit"],
|
values: ["non-segwit", "segwit"],
|
||||||
"value-names": {},
|
"value-names": {},
|
||||||
@@ -91,7 +93,8 @@ const bitcoinProperties = {
|
|||||||
},
|
},
|
||||||
servertimeout: {
|
servertimeout: {
|
||||||
name: "Rpc Server Timeout",
|
name: "Rpc Server Timeout",
|
||||||
description: "Number of seconds after which an uncompleted RPC call will time out.",
|
description:
|
||||||
|
"Number of seconds after which an uncompleted RPC call will time out.",
|
||||||
type: "number",
|
type: "number",
|
||||||
nullable: false,
|
nullable: false,
|
||||||
range: "[5,300]",
|
range: "[5,300]",
|
||||||
@@ -195,7 +198,8 @@ const bitcoinProperties = {
|
|||||||
type: "number",
|
type: "number",
|
||||||
nullable: false,
|
nullable: false,
|
||||||
name: "Max Mempool Size",
|
name: "Max Mempool Size",
|
||||||
description: "Keep the transaction memory pool below <n> megabytes.",
|
description:
|
||||||
|
"Keep the transaction memory pool below <n> megabytes.",
|
||||||
range: "[1,*)",
|
range: "[1,*)",
|
||||||
integral: true,
|
integral: true,
|
||||||
units: "MiB",
|
units: "MiB",
|
||||||
@@ -205,7 +209,8 @@ const bitcoinProperties = {
|
|||||||
type: "number",
|
type: "number",
|
||||||
nullable: false,
|
nullable: false,
|
||||||
name: "Mempool Expiration",
|
name: "Mempool Expiration",
|
||||||
description: "Do not keep transactions in the mempool longer than <n> hours.",
|
description:
|
||||||
|
"Do not keep transactions in the mempool longer than <n> hours.",
|
||||||
range: "[1,*)",
|
range: "[1,*)",
|
||||||
integral: true,
|
integral: true,
|
||||||
units: "Hr",
|
units: "Hr",
|
||||||
@@ -221,7 +226,8 @@ const bitcoinProperties = {
|
|||||||
listen: {
|
listen: {
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
name: "Make Public",
|
name: "Make Public",
|
||||||
description: "Allow other nodes to find your server on the network.",
|
description:
|
||||||
|
"Allow other nodes to find your server on the network.",
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
onlyconnect: {
|
onlyconnect: {
|
||||||
@@ -261,7 +267,8 @@ const bitcoinProperties = {
|
|||||||
type: "number",
|
type: "number",
|
||||||
nullable: true,
|
nullable: true,
|
||||||
name: "Port",
|
name: "Port",
|
||||||
description: "Port that peer is listening on for inbound p2p connections",
|
description:
|
||||||
|
"Port that peer is listening on for inbound p2p connections",
|
||||||
range: "[0,65535]",
|
range: "[0,65535]",
|
||||||
integral: true,
|
integral: true,
|
||||||
},
|
},
|
||||||
@@ -285,7 +292,8 @@ const bitcoinProperties = {
|
|||||||
pruning: {
|
pruning: {
|
||||||
type: "union",
|
type: "union",
|
||||||
name: "Pruning Settings",
|
name: "Pruning Settings",
|
||||||
description: "Blockchain Pruning Options\nReduce the blockchain size on disk\n",
|
description:
|
||||||
|
"Blockchain Pruning Options\nReduce the blockchain size on disk\n",
|
||||||
warning:
|
warning:
|
||||||
"If you set pruning to Manual and your disk is smaller than the total size of the blockchain, you MUST have something running that prunes these blocks or you may overfill your disk!\nDisabling pruning will convert your node into a full archival node. This requires a resync of the entire blockchain, a process that may take several days. Make sure you have enough free disk space or you may fill up your disk.\n",
|
"If you set pruning to Manual and your disk is smaller than the total size of the blockchain, you MUST have something running that prunes these blocks or you may overfill your disk!\nDisabling pruning will convert your node into a full archival node. This requires a resync of the entire blockchain, a process that may take several days. Make sure you have enough free disk space or you may fill up your disk.\n",
|
||||||
tag: {
|
tag: {
|
||||||
@@ -307,7 +315,8 @@ const bitcoinProperties = {
|
|||||||
nullable: false,
|
nullable: false,
|
||||||
name: "Max Chain Size",
|
name: "Max Chain Size",
|
||||||
description: "Limit of blockchain size on disk.",
|
description: "Limit of blockchain size on disk.",
|
||||||
warning: "Increasing this value will require re-syncing your node.",
|
warning:
|
||||||
|
"Increasing this value will require re-syncing your node.",
|
||||||
default: 550,
|
default: 550,
|
||||||
range: "[550,1000000)",
|
range: "[550,1000000)",
|
||||||
integral: true,
|
integral: true,
|
||||||
@@ -360,7 +369,8 @@ const bitcoinProperties = {
|
|||||||
name: "Serve Bloom Filters to Peers",
|
name: "Serve Bloom Filters to Peers",
|
||||||
description:
|
description:
|
||||||
"Peers have the option of setting filters on each connection they make after the version handshake has completed. Bloom filters are for clients implementing SPV (Simplified Payment Verification) that want to check that block headers connect together correctly, without needing to verify the full blockchain. The client must trust that the transactions in the chain are in fact valid. It is highly recommended AGAINST using for anything except Bisq integration.",
|
"Peers have the option of setting filters on each connection they make after the version handshake has completed. Bloom filters are for clients implementing SPV (Simplified Payment Verification) that want to check that block headers connect together correctly, without needing to verify the full blockchain. The client must trust that the transactions in the chain are in fact valid. It is highly recommended AGAINST using for anything except Bisq integration.",
|
||||||
warning: "This is ONLY for use with Bisq integration, please use Block Filters for all other applications.",
|
warning:
|
||||||
|
"This is ONLY for use with Bisq integration, please use Block Filters for all other applications.",
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -371,22 +381,36 @@ const bitcoinProperties = {
|
|||||||
} as const;
|
} as const;
|
||||||
type BitcoinProperties = typeof bitcoinProperties;
|
type BitcoinProperties = typeof bitcoinProperties;
|
||||||
const anyValue: unknown = "";
|
const anyValue: unknown = "";
|
||||||
const _testBoolean: boolean = anyValue as PM.GuardAll<BitcoinProperties["rpc"]["spec"]["enable"]>;
|
const _testBoolean: boolean = anyValue as PM.GuardAll<
|
||||||
|
BitcoinProperties["rpc"]["spec"]["enable"]
|
||||||
|
>;
|
||||||
// @ts-expect-error Boolean can't be a string
|
// @ts-expect-error Boolean can't be a string
|
||||||
const _testBooleanBad: string = anyValue as PM.GuardAll<BitcoinProperties["rpc"]["spec"]["enable"]>;
|
const _testBooleanBad: string = anyValue as PM.GuardAll<
|
||||||
const _testString: string = anyValue as PM.GuardAll<BitcoinProperties["rpc"]["spec"]["username"]>;
|
BitcoinProperties["rpc"]["spec"]["enable"]
|
||||||
|
>;
|
||||||
|
const _testString: string = anyValue as PM.GuardAll<
|
||||||
|
BitcoinProperties["rpc"]["spec"]["username"]
|
||||||
|
>;
|
||||||
// @ts-expect-error string can't be a boolean
|
// @ts-expect-error string can't be a boolean
|
||||||
const _testStringBad: boolean = anyValue as PM.GuardAll<BitcoinProperties["rpc"]["spec"]["username"]>;
|
const _testStringBad: boolean = anyValue as PM.GuardAll<
|
||||||
const _testNumber: number = anyValue as PM.GuardAll<BitcoinProperties["advanced"]["spec"]["dbcache"]>;
|
BitcoinProperties["rpc"]["spec"]["username"]
|
||||||
|
>;
|
||||||
|
const _testNumber: number = anyValue as PM.GuardAll<
|
||||||
|
BitcoinProperties["advanced"]["spec"]["dbcache"]
|
||||||
|
>;
|
||||||
// @ts-expect-error Number can't be string
|
// @ts-expect-error Number can't be string
|
||||||
const _testNumberBad: string = anyValue as PM.GuardAll<BitcoinProperties["advanced"]["spec"]["dbcache"]>;
|
const _testNumberBad: string = anyValue as PM.GuardAll<
|
||||||
|
BitcoinProperties["advanced"]["spec"]["dbcache"]
|
||||||
|
>;
|
||||||
const _testObject: {
|
const _testObject: {
|
||||||
enable: boolean;
|
enable: boolean;
|
||||||
avoidpartialspends: boolean;
|
avoidpartialspends: boolean;
|
||||||
discardfee: number;
|
discardfee: number;
|
||||||
} = anyValue as PM.GuardAll<BitcoinProperties["wallet"]>;
|
} = anyValue as PM.GuardAll<BitcoinProperties["wallet"]>;
|
||||||
// @ts-expect-error Boolean can't be object
|
// @ts-expect-error Boolean can't be object
|
||||||
const _testObjectBad: boolean = anyValue as PM.GuardAll<BitcoinProperties["wallet"]>;
|
const _testObjectBad: boolean = anyValue as PM.GuardAll<
|
||||||
|
BitcoinProperties["wallet"]
|
||||||
|
>;
|
||||||
const _testObjectNested: { test: { a: boolean } } = anyValue as PM.GuardAll<{
|
const _testObjectNested: { test: { a: boolean } } = anyValue as PM.GuardAll<{
|
||||||
readonly type: "object";
|
readonly type: "object";
|
||||||
readonly spec: {
|
readonly spec: {
|
||||||
@@ -411,7 +435,7 @@ const _testListBad: readonly number[] = anyValue as PM.GuardAll<{
|
|||||||
subtype: "string";
|
subtype: "string";
|
||||||
default: [];
|
default: [];
|
||||||
}>;
|
}>;
|
||||||
const _testPointer: { _UNKNOWN: "Pointer" } = anyValue as PM.GuardAll<{
|
const _testPointer: string | null = anyValue as PM.GuardAll<{
|
||||||
type: "pointer";
|
type: "pointer";
|
||||||
}>;
|
}>;
|
||||||
const testUnionValue = anyValue as PM.GuardAll<{
|
const testUnionValue = anyValue as PM.GuardAll<{
|
||||||
@@ -419,7 +443,8 @@ const testUnionValue = anyValue as PM.GuardAll<{
|
|||||||
tag: {
|
tag: {
|
||||||
id: "mode";
|
id: "mode";
|
||||||
name: "Pruning Mode";
|
name: "Pruning Mode";
|
||||||
description: '- Disabled: Disable pruning\n- Automatic: Limit blockchain size on disk to a certain number of megabytes\n- Manual: Prune blockchain with the "pruneblockchain" RPC\n';
|
description:
|
||||||
|
'- Disabled: Disable pruning\n- Automatic: Limit blockchain size on disk to a certain number of megabytes\n- Manual: Prune blockchain with the "pruneblockchain" RPC\n';
|
||||||
"variant-names": {
|
"variant-names": {
|
||||||
disabled: "Disabled";
|
disabled: "Disabled";
|
||||||
automatic: "Automatic";
|
automatic: "Automatic";
|
||||||
@@ -460,17 +485,17 @@ const _testUnion:
|
|||||||
| { mode: "disabled" }
|
| { mode: "disabled" }
|
||||||
| { mode: "automatic"; size: number }
|
| { mode: "automatic"; size: number }
|
||||||
| {
|
| {
|
||||||
mode: "manual";
|
mode: "manual";
|
||||||
size: number;
|
size: number;
|
||||||
} = testUnionValue;
|
} = testUnionValue;
|
||||||
//@ts-expect-error Bad mode name
|
//@ts-expect-error Bad mode name
|
||||||
const _testUnionBadUnion:
|
const _testUnionBadUnion:
|
||||||
| { mode: "disabled" }
|
| { mode: "disabled" }
|
||||||
| { mode: "bad"; size: number }
|
| { mode: "bad"; size: number }
|
||||||
| {
|
| {
|
||||||
mode: "manual";
|
mode: "manual";
|
||||||
size: number;
|
size: number;
|
||||||
} = testUnionValue;
|
} = testUnionValue;
|
||||||
const _testAll: PM.TypeFromProps<BitcoinProperties> = anyValue as {
|
const _testAll: PM.TypeFromProps<BitcoinProperties> = anyValue as {
|
||||||
// deno-lint-ignore no-explicit-any
|
// deno-lint-ignore no-explicit-any
|
||||||
"peer-tor-address": any;
|
"peer-tor-address": any;
|
||||||
@@ -514,9 +539,9 @@ const _testAll: PM.TypeFromProps<BitcoinProperties> = anyValue as {
|
|||||||
| { mode: "disabled" }
|
| { mode: "disabled" }
|
||||||
| { mode: "automatic"; size: number }
|
| { mode: "automatic"; size: number }
|
||||||
| {
|
| {
|
||||||
mode: "manual";
|
mode: "manual";
|
||||||
size: number;
|
size: number;
|
||||||
};
|
};
|
||||||
blockfilters: {
|
blockfilters: {
|
||||||
blockfilterindex: boolean;
|
blockfilterindex: boolean;
|
||||||
peerblockfilters: boolean;
|
peerblockfilters: boolean;
|
||||||
@@ -572,24 +597,28 @@ const { test } = Deno;
|
|||||||
test("Generate 1", () => {
|
test("Generate 1", () => {
|
||||||
const random = randWithSeed(1);
|
const random = randWithSeed(1);
|
||||||
const options = { random };
|
const options = { random };
|
||||||
const generated = PM.generateDefault({ charset: "a-z,B-X,2-5", len: 100 }, options);
|
const generated = PM.generateDefault(
|
||||||
|
{ charset: "a-z,B-X,2-5", len: 100 },
|
||||||
|
options,
|
||||||
|
);
|
||||||
expect(generated.length).toBe(100);
|
expect(generated.length).toBe(100);
|
||||||
expect(generated).toBe(
|
expect(generated).toBe(
|
||||||
"WwwgjGRkvDaGQSLeKTtlOmdDbXoCBkOn3dxUvkKkrlOFd4FbKuvIosvfPTQhbWCTQakqnwpoHmPnbgyK5CGtSQyGhxEGLjS3oKko"
|
"WwwgjGRkvDaGQSLeKTtlOmdDbXoCBkOn3dxUvkKkrlOFd4FbKuvIosvfPTQhbWCTQakqnwpoHmPnbgyK5CGtSQyGhxEGLjS3oKko",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
test("Generate Tests", () => {
|
test("Generate Tests", () => {
|
||||||
const random = randWithSeed(2);
|
const random = randWithSeed(2);
|
||||||
const options = { random };
|
const options = { random };
|
||||||
expect(PM.generateDefault({ charset: "0-1", len: 100 }, options)).toBe(
|
expect(PM.generateDefault({ charset: "0-1", len: 100 }, options)).toBe(
|
||||||
"0000110010000000000011110000010010000011101111001000000000000000100001101000010000001000010000010110"
|
"0000110010000000000011110000010010000011101111001000000000000000100001101000010000001000010000010110",
|
||||||
);
|
);
|
||||||
expect(PM.generateDefault({ charset: "a-z", len: 100 }, options)).toBe(
|
expect(PM.generateDefault({ charset: "a-z", len: 100 }, options)).toBe(
|
||||||
"qipnycbqmqdtflrhnckgrhftrqnvxbhyyfehpvficljseasxwdyleacmjqemmpnuotkwzlsqdumuaaksxykchljgdoslrfubhepr"
|
"qipnycbqmqdtflrhnckgrhftrqnvxbhyyfehpvficljseasxwdyleacmjqemmpnuotkwzlsqdumuaaksxykchljgdoslrfubhepr",
|
||||||
);
|
|
||||||
expect(PM.generateDefault({ charset: "a,b,c,d,f,g", len: 100 }, options)).toBe(
|
|
||||||
"bagbafcgaaddcabdfadccaadfbddffdcfccfbafbddbbfcdggfcgaffdbcgcagcfbdbfaagbfgfccdbfdfbdagcfdcabbdffaffc"
|
|
||||||
);
|
);
|
||||||
|
expect(PM.generateDefault({ charset: "a,b,c,d,f,g", len: 100 }, options))
|
||||||
|
.toBe(
|
||||||
|
"bagbafcgaaddcabdfadccaadfbddffdcfccfbafbddbbfcdggfcgaffdbcgcagcfbdbfaagbfgfccdbfdfbdagcfdcabbdffaffc",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -601,24 +630,26 @@ const { test } = Deno;
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("A default that is invalid according to the tests", () => {
|
test("A default that is invalid according to the tests", () => {
|
||||||
const checker = PM.typeFromProps({
|
const checker = PM.typeFromProps(
|
||||||
pubkey_whitelist: {
|
{
|
||||||
name: "Pubkey Whitelist (hex)",
|
pubkey_whitelist: {
|
||||||
description:
|
name: "Pubkey Whitelist (hex)",
|
||||||
"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.",
|
description:
|
||||||
type: "list",
|
"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.",
|
||||||
range: "[1,*)",
|
type: "list",
|
||||||
subtype: "string",
|
range: "[1,*)",
|
||||||
spec: {
|
subtype: "string",
|
||||||
masked: false,
|
spec: {
|
||||||
placeholder: "hex (not npub) pubkey",
|
masked: false,
|
||||||
pattern: "[0-9a-fA-F]{3}",
|
placeholder: "hex (not npub) pubkey",
|
||||||
"pattern-description":
|
pattern: "[0-9a-fA-F]{3}",
|
||||||
"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.",
|
"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 []
|
||||||
},
|
},
|
||||||
default: [] as string[], // [] as string []
|
} as const,
|
||||||
},
|
);
|
||||||
} as const);
|
|
||||||
|
|
||||||
checker.unsafeCast({
|
checker.unsafeCast({
|
||||||
pubkey_whitelist: ["aaa"],
|
pubkey_whitelist: ["aaa"],
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import { matches } from "../dependencies.ts";
|
import { matches } from "../dependencies.ts";
|
||||||
import { ConfigSpec, ValueSpec as ValueSpecAny } from "../types/config-types.ts";
|
import {
|
||||||
|
ConfigSpec,
|
||||||
|
ValueSpec as ValueSpecAny,
|
||||||
|
} from "../types/config-types.ts";
|
||||||
|
|
||||||
type TypeBoolean = "boolean";
|
type TypeBoolean = "boolean";
|
||||||
type TypeString = "string";
|
type TypeString = "string";
|
||||||
@@ -66,14 +69,15 @@ type GuardUnion<A> =
|
|||||||
unknown
|
unknown
|
||||||
|
|
||||||
type _<T> = T;
|
type _<T> = T;
|
||||||
export type GuardAll<A> = GuardNumber<A> &
|
export type GuardAll<A> =
|
||||||
GuardString<A> &
|
& GuardNumber<A>
|
||||||
GuardBoolean<A> &
|
& GuardString<A>
|
||||||
GuardObject<A> &
|
& GuardBoolean<A>
|
||||||
GuardList<A> &
|
& GuardObject<A>
|
||||||
GuardPointer<A> &
|
& GuardList<A>
|
||||||
GuardUnion<A> &
|
& GuardPointer<A>
|
||||||
GuardEnum<A>;
|
& GuardUnion<A>
|
||||||
|
& GuardEnum<A>;
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
// deno-fmt-ignore
|
// deno-fmt-ignore
|
||||||
export type TypeFromProps<A> =
|
export type TypeFromProps<A> =
|
||||||
@@ -109,23 +113,32 @@ function charRange(value = "") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @param generate.charset Pattern like "a-z" or "a-z,1-5"
|
* @param generate.charset Pattern like "a-z" or "a-z,1-5"
|
||||||
* @param generate.len Length to make random variable
|
* @param generate.len Length to make random variable
|
||||||
* @param param1
|
* @param param1
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function generateDefault(generate: { charset: string; len: number }, { random = () => Math.random() } = {}) {
|
export function generateDefault(
|
||||||
const validCharSets: number[][] = generate.charset.split(",").map(charRange).filter(Array.isArray);
|
generate: { charset: string; len: number },
|
||||||
if (validCharSets.length === 0) throw new Error("Expecing that we have a valid charset");
|
{ random = () => Math.random() } = {},
|
||||||
const max = validCharSets.reduce((acc, x) => x.reduce((x, y) => Math.max(x, y), acc), 0);
|
) {
|
||||||
|
const validCharSets: number[][] = generate.charset.split(",").map(charRange)
|
||||||
|
.filter(Array.isArray);
|
||||||
|
if (validCharSets.length === 0) {
|
||||||
|
throw new Error("Expecing that we have a valid charset");
|
||||||
|
}
|
||||||
|
const max = validCharSets.reduce(
|
||||||
|
(acc, x) => x.reduce((x, y) => Math.max(x, y), acc),
|
||||||
|
0,
|
||||||
|
);
|
||||||
let i = 0;
|
let i = 0;
|
||||||
const answer: string[] = Array(generate.len);
|
const answer: string[] = Array(generate.len);
|
||||||
while (i < generate.len) {
|
while (i < generate.len) {
|
||||||
const nextValue = Math.round(random() * max);
|
const nextValue = Math.round(random() * max);
|
||||||
const inRange = validCharSets.reduce(
|
const inRange = validCharSets.reduce(
|
||||||
(acc, [lower, upper]) => acc || (nextValue >= lower && nextValue <= upper),
|
(acc, [lower, upper]) =>
|
||||||
false
|
acc || (nextValue >= lower && nextValue <= upper),
|
||||||
|
false,
|
||||||
);
|
);
|
||||||
if (!inRange) continue;
|
if (!inRange) continue;
|
||||||
answer[i] = String.fromCharCode(nextValue);
|
answer[i] = String.fromCharCode(nextValue);
|
||||||
@@ -144,12 +157,28 @@ export function matchNumberWithRange(range: string) {
|
|||||||
const [, left, leftValue, , rightValue, , right] = matched;
|
const [, left, leftValue, , rightValue, , right] = matched;
|
||||||
return matches.number
|
return matches.number
|
||||||
.validate(
|
.validate(
|
||||||
leftValue === "*" ? (_) => true : left === "[" ? (x) => x >= Number(leftValue) : (x) => x > Number(leftValue),
|
leftValue === "*"
|
||||||
leftValue === "*" ? "any" : left === "[" ? `greaterThanOrEqualTo${leftValue}` : `greaterThan${leftValue}`
|
? (_) => true
|
||||||
|
: left === "["
|
||||||
|
? (x) => x >= Number(leftValue)
|
||||||
|
: (x) => x > Number(leftValue),
|
||||||
|
leftValue === "*"
|
||||||
|
? "any"
|
||||||
|
: left === "["
|
||||||
|
? `greaterThanOrEqualTo${leftValue}`
|
||||||
|
: `greaterThan${leftValue}`,
|
||||||
)
|
)
|
||||||
.validate(
|
.validate(
|
||||||
rightValue === "*" ? (_) => true : right === "]" ? (x) => x <= Number(rightValue) : (x) => x < Number(rightValue),
|
rightValue === "*"
|
||||||
rightValue === "*" ? "any" : right === "]" ? `lessThanOrEqualTo${rightValue}` : `lessThan${rightValue}`
|
? (_) => true
|
||||||
|
: right === "]"
|
||||||
|
? (x) => x <= Number(rightValue)
|
||||||
|
: (x) => x < Number(rightValue),
|
||||||
|
rightValue === "*"
|
||||||
|
? "any"
|
||||||
|
: right === "]"
|
||||||
|
? `lessThanOrEqualTo${rightValue}`
|
||||||
|
: `lessThan${rightValue}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
function withIntegral(parser: matches.Parser<unknown, number>, value: unknown) {
|
function withIntegral(parser: matches.Parser<unknown, number>, value: unknown) {
|
||||||
@@ -164,10 +193,18 @@ function withRange(value: unknown) {
|
|||||||
}
|
}
|
||||||
return matches.number;
|
return matches.number;
|
||||||
}
|
}
|
||||||
const isGenerator = matches.shape({ charset: matches.string, len: matches.number }).test;
|
const isGenerator =
|
||||||
function defaultNullable<A>(parser: matches.Parser<unknown, A>, value: unknown) {
|
matches.shape({ charset: matches.string, len: matches.number }).test;
|
||||||
|
function defaultNullable<A>(
|
||||||
|
parser: matches.Parser<unknown, A>,
|
||||||
|
value: unknown,
|
||||||
|
) {
|
||||||
if (matchDefault.test(value)) {
|
if (matchDefault.test(value)) {
|
||||||
if (isGenerator(value.default)) return parser.defaultTo(parser.unsafeCast(generateDefault(value.default)));
|
if (isGenerator(value.default)) {
|
||||||
|
return parser.defaultTo(
|
||||||
|
parser.unsafeCast(generateDefault(value.default)),
|
||||||
|
);
|
||||||
|
}
|
||||||
return parser.defaultTo(value.default);
|
return parser.defaultTo(value.default);
|
||||||
}
|
}
|
||||||
if (matchNullable.test(value)) return parser.optional();
|
if (matchNullable.test(value)) return parser.optional();
|
||||||
@@ -182,7 +219,9 @@ function defaultNullable<A>(parser: matches.Parser<unknown, A>, value: unknown)
|
|||||||
* @param value
|
* @param value
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function guardAll<A extends ValueSpecAny>(value: A): matches.Parser<unknown, GuardAll<A>> {
|
export function guardAll<A extends ValueSpecAny>(
|
||||||
|
value: A,
|
||||||
|
): matches.Parser<unknown, GuardAll<A>> {
|
||||||
if (!isType.test(value)) {
|
if (!isType.test(value)) {
|
||||||
// deno-lint-ignore no-explicit-any
|
// deno-lint-ignore no-explicit-any
|
||||||
return matches.unknown as any;
|
return matches.unknown as any;
|
||||||
@@ -199,7 +238,7 @@ export function guardAll<A extends ValueSpecAny>(value: A): matches.Parser<unkno
|
|||||||
case "number":
|
case "number":
|
||||||
return defaultNullable(
|
return defaultNullable(
|
||||||
withIntegral(withRange(value), value),
|
withIntegral(withRange(value), value),
|
||||||
value
|
value,
|
||||||
// deno-lint-ignore no-explicit-any
|
// deno-lint-ignore no-explicit-any
|
||||||
) as any;
|
) as any;
|
||||||
|
|
||||||
@@ -213,7 +252,9 @@ export function guardAll<A extends ValueSpecAny>(value: A): matches.Parser<unkno
|
|||||||
|
|
||||||
case "list": {
|
case "list": {
|
||||||
const spec = (matchSpec.test(value) && value.spec) || {};
|
const spec = (matchSpec.test(value) && value.spec) || {};
|
||||||
const rangeValidate = (matchRange.test(value) && matchNumberWithRange(value.range).test) || (() => true);
|
const rangeValidate =
|
||||||
|
(matchRange.test(value) && matchNumberWithRange(value.range).test) ||
|
||||||
|
(() => true);
|
||||||
|
|
||||||
const subtype = matchSubType.unsafeCast(value).subtype;
|
const subtype = matchSubType.unsafeCast(value).subtype;
|
||||||
return defaultNullable(
|
return defaultNullable(
|
||||||
@@ -221,7 +262,7 @@ export function guardAll<A extends ValueSpecAny>(value: A): matches.Parser<unkno
|
|||||||
// deno-lint-ignore no-explicit-any
|
// deno-lint-ignore no-explicit-any
|
||||||
.arrayOf(guardAll({ type: subtype, ...spec } as any))
|
.arrayOf(guardAll({ type: subtype, ...spec } as any))
|
||||||
.validate((x) => rangeValidate(x.length), "valid length"),
|
.validate((x) => rangeValidate(x.length), "valid length"),
|
||||||
value
|
value,
|
||||||
// deno-lint-ignore no-explicit-any
|
// deno-lint-ignore no-explicit-any
|
||||||
) as any;
|
) as any;
|
||||||
}
|
}
|
||||||
@@ -229,7 +270,7 @@ export function guardAll<A extends ValueSpecAny>(value: A): matches.Parser<unkno
|
|||||||
if (matchValues.test(value)) {
|
if (matchValues.test(value)) {
|
||||||
return defaultNullable(
|
return defaultNullable(
|
||||||
matches.literals(value.values[0], ...value.values),
|
matches.literals(value.values[0], ...value.values),
|
||||||
value
|
value,
|
||||||
// deno-lint-ignore no-explicit-any
|
// deno-lint-ignore no-explicit-any
|
||||||
) as any;
|
) as any;
|
||||||
}
|
}
|
||||||
@@ -242,8 +283,10 @@ export function guardAll<A extends ValueSpecAny>(value: A): matches.Parser<unkno
|
|||||||
if (matchUnion.test(value)) {
|
if (matchUnion.test(value)) {
|
||||||
return matches.some(
|
return matches.some(
|
||||||
...Object.entries(value.variants).map(([variant, spec]) =>
|
...Object.entries(value.variants).map(([variant, spec]) =>
|
||||||
matches.shape({ [value.tag.id]: matches.literal(variant) }).concat(typeFromProps(spec))
|
matches.shape({ [value.tag.id]: matches.literal(variant) }).concat(
|
||||||
) // deno-lint-ignore no-explicit-any
|
typeFromProps(spec),
|
||||||
|
)
|
||||||
|
), // deno-lint-ignore no-explicit-any
|
||||||
) as any;
|
) as any;
|
||||||
}
|
}
|
||||||
// deno-lint-ignore no-explicit-any
|
// deno-lint-ignore no-explicit-any
|
||||||
@@ -261,11 +304,17 @@ export function guardAll<A extends ValueSpecAny>(value: A): matches.Parser<unkno
|
|||||||
* @param valueDictionary
|
* @param valueDictionary
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function typeFromProps<A extends ConfigSpec>(valueDictionary: A): matches.Parser<unknown, TypeFromProps<A>> {
|
export function typeFromProps<A extends ConfigSpec>(
|
||||||
|
valueDictionary: A,
|
||||||
|
): matches.Parser<unknown, TypeFromProps<A>> {
|
||||||
// deno-lint-ignore no-explicit-any
|
// deno-lint-ignore no-explicit-any
|
||||||
if (!recordString.test(valueDictionary)) return matches.unknown as any;
|
if (!recordString.test(valueDictionary)) return matches.unknown as any;
|
||||||
return matches.shape(
|
return matches.shape(
|
||||||
Object.fromEntries(Object.entries(valueDictionary).map(([key, value]) => [key, guardAll(value)]))
|
Object.fromEntries(
|
||||||
|
Object.entries(valueDictionary).map((
|
||||||
|
[key, value],
|
||||||
|
) => [key, guardAll(value)]),
|
||||||
|
),
|
||||||
// deno-lint-ignore no-explicit-any
|
// deno-lint-ignore no-explicit-any
|
||||||
) as any;
|
) as any;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user