Fix StartOS 0.4 TypeScript packaging to match SDK API

This commit is contained in:
MacPro
2026-04-09 15:10:44 -05:00
parent 68ec875ee7
commit 8298c083c7
3436 changed files with 867051 additions and 92 deletions
+171
View File
@@ -0,0 +1,171 @@
# Changelog
## 0.4.0-beta.66 (2026-03-24)
- **Breaking:** `withPgDump()` replaces `pgdata` with required `mountpoint` + `pgdataPath`
- Passwordless/trust auth support for `withPgDump()` and `withMysqlDump()`
- New options: `pgOptions` for postgres, `mysqldOptions` for mysql/mariadb
- Fixed MariaDB backup/restore support
## 0.4.0-beta.65 (2026-03-23)
### Added
- `Backups.withPgDump()`: dump-based PostgreSQL backup using `pg_dump`/`pg_restore`, replacing raw volume rsync of PG data directories
- `Backups.withMysqlDump()`: dump-based MySQL/MariaDB backup using `mysqldump`/`mysql`
- Password configs accept `string | (() => string | Promise<string>)` for deferred resolution during restore
## 0.4.0-beta.63 — StartOS v0.4.0-alpha.22 (2026-03-22)
### Fixed
- Fixed `createTask` failing when input values are undefined
- Fixed daemon lifecycle cleanup and error logging improvements
- Replaced fire-and-forget restart loop in `Daemon` with tracked `AbortController`
- Fixed graceful shutdown for subcontainer daemons
- Fixed rsync progress regex never matching, spamming logs during backup
- Fixed rsync backup bugs and optimized flags for encrypted CIFS targets
- Fixed types in `inputSpecConstants`, `StartSdk`, and exports
## 0.4.0-beta.62 (2026-03-19)
### Fixed
- Fixed `Value.dynamicSelect` and `Value.dynamicMultiselect` crashing with `z.union([])` when `values` is empty (zod v4 compatibility)
### Added
- `FileHelper.xml`: file helper for XML files using `fast-xml-parser`
- `smtpShape`: typed zod schema for persisting SMTP selection in store file models, replacing direct use of `smtpInputSpec.validator` which caused cross-zod-instance errors
## 0.4.0-beta.61 — StartOS v0.4.0-alpha.21 (2026-03-16)
### Fixed
- Fixed bug where leaving the effect context triggered consts
## 0.4.0-beta.60 — StartOS v0.4.0-alpha.20 (2026-03-16)
### Added
- Tunnel TS type exports and port forward labels
- Secure Boot MOK key enrollment fields in `SetupInfo`
### Changed
- Consolidated `Watchable` base class with generic `map`/`eq` support; renamed `call` to `fetch`
- Moved `GetServiceManifest` and `GetSslCertificate` from `package/` to `base/`
- Simplified `getServiceInterface`, `getServiceInterfaces`, `GetOutboundGateway`, `GetSystemSmtp`, and `fileHelper` using `Watchable` base class
- Simplified SDK Makefile with rsync
### Fixed
- Added `restart_again` flag to `DesiredStatus::Restarting`
## 0.4.0-beta.59 — StartOS v0.4.0-alpha.20 (2026-03-06)
### Added
- Support for preferred external ports besides 443
- Bridge filter kind on service interfaces
### Fixed
- Merge version ranges when adding existing package signer
- Task fix for action task system
## 0.4.0-beta.56 — StartOS v0.4.0-alpha.19 (2026-02-02)
### Added
- `getOutboundGateway` effect and SDK wrapper
- Improved service version migration and data version handling
- `zod-deep-partial` integration with `partialValidator` on `InputSpec`
- SMTP rework with improved provider variants and system SMTP spec
### Changed
- Migrated from `ts-matches` to `zod` across all TypeScript packages
- Builder-style `InputSpec` API with prefill plumbing
- Split `row_actions` into `remove_action` and `overflow_actions` for URL plugins
### Fixed
- Scoped public domain to single binding and return single port check
- Preserved `z` namespace types for SDK consumers
- `--arch` flag falls back to emulation when native image unavailable
## 0.4.0-beta.54 — StartOS v0.4.0-alpha.18 (2026-01-27)
### Added
- Device info RPC
- Hardware acceleration and NVIDIA card support on nonfree images
### Changed
- Consolidated setup flow
- Improved SDK abort handling and `InputSpec` filtering
## 0.4.0-beta.49 — StartOS v0.4.0-alpha.17 (2026-01-10)
### Added
- JSDoc comments on all consumer-facing APIs
- StartTunnel random subnet support
- Port 80 to 5443 tunnel mapping
### Fixed
- `EffectCreator` type corrections
- Allow multiple equal signs in ENV `FileHelper` values
- Miscellaneous alpha.16 follow-up fixes
## 0.4.0-beta.45 — StartOS v0.4.0-alpha.16 (2025-12-18)
### Added
- `map` and `eq` on `getServiceInterface` watcher
- Flavor-aware version range handling
### Changed
- Refactored `StatusInfo` types
- Improved shutdown ordering for daemons
- Improved StartTunnel validation and garbage collection
## 0.4.0-beta.43 — StartOS v0.4.0-alpha.15 (2025-11-26)
### Fixed
- Minor bugfixes for alpha.14
## 0.4.0-beta.42 — StartOS v0.4.0-alpha.14 (2025-11-20)
### Fixed
- Bugfixes for alpha.13
## 0.4.0-beta.41 — StartOS v0.4.0-alpha.13 (2025-11-15)
### Fixed
- Bugfixes for alpha.12
## 0.4.0-beta.40 — StartOS v0.4.0-alpha.12 (2025-11-07)
### Added
- StartTunnel integration
- Configurable `textarea` rows in `InputSpec`
## 0.4.0-beta.39 — StartOS v0.4.0-alpha.11 (2025-09-24)
### Added
- Gateway limiting for StartTunnel
- Improved copy UX around Tor SSL
### Changed
- SDK type updates and internal improvements
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Start9 Labs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+103
View File
@@ -0,0 +1,103 @@
# Start SDK
The Start SDK (`@start9labs/start-sdk`) is a TypeScript framework for packaging services to run on [StartOS](https://github.com/Start9Labs/start-os). It provides a strongly-typed, builder-pattern API for defining every aspect of a service package: manifests, daemons, health checks, networking interfaces, actions, backups, dependencies, configuration, and more.
## Features
- **Type-safe manifest definitions** - Declare your service's identity, metadata, images, volumes, and dependencies with full TypeScript inference.
- **Daemon management** - Define multi-process topologies with startup ordering, ready probes, and graceful shutdown via `Daemons.of().addDaemon()`.
- **Health checks** - Built-in checks for TCP port listening, HTTP(S) endpoints, and custom scripts, with configurable polling strategies (fixed interval, cooldown, adaptive).
- **Network interfaces** - Bind ports, export UI/API/P2P interfaces, and manage hostnames with MultiHost and ServiceInterfaceBuilder.
- **User actions** - Create interactive operations with validated form inputs (text, select, toggle, list, union/variants, and more) that users can trigger from the StartOS UI.
- **Backup and restore** - Rsync-based volume backups with exclude patterns and custom sync paths.
- **Dependency management** - Declare inter-service dependencies with version ranges, health check requirements, and volume mounts.
- **Configuration file helpers** - Read, write, and merge JSON, YAML, TOML, INI, and ENV files with type-safe `FileHelper`.
- **Reactive subscriptions** - Watch for changes to container IPs, SSL certificates, SMTP config, service status, and more with `const()`, `once()`, `watch()`, `onChange()`, and `waitFor()` patterns.
- **Extended versioning (ExVer)** - Flavor-aware semantic versioning with range matching, supporting independent upstream and downstream version tracking.
- **Internationalization** - Built-in i18n support with locale fallback and parameter substitution.
- **Container execution** - Run commands in subcontainers with volume mounts, environment variables, and entrypoint overrides.
- **Plugin system** - Extensible plugin architecture (e.g. `url-v0` for URL management).
## Quick Start
```typescript
import { setupManifest, buildManifest } from '@start9labs/start-sdk'
const manifest = setupManifest({
id: 'my-service',
title: 'My Service',
license: 'MIT',
// ...
})
export default buildManifest(manifest)
```
The primary entry point is the `StartSdk` facade:
```typescript
import { StartSdk } from '@start9labs/start-sdk'
import { manifest } from './manifest'
export const sdk = StartSdk.of().withManifest(manifest).build(true)
```
From there, `sdk` exposes the full toolkit:
```typescript
// Define daemons
export const main = sdk.setupMain(async ({ effects }) =>
sdk.Daemons.of(effects)
.addDaemon('primary', { /* ... */ })
)
// Define actions
export const setName = sdk.Action.withInput('set-name', /* ... */)
// Define interfaces
export const setInterfaces = sdk.setupInterfaces(async ({ effects }) => {
const multi = sdk.MultiHost.of(effects, 'web')
const origin = await multi.bindPort(80, { protocol: 'http' })
const ui = sdk.createInterface(effects, { name: 'Web UI', id: 'ui', /* ... */ })
return [await origin.export([ui])]
})
// Define backups
export const { createBackup, restoreBackup } = sdk.setupBackups(
async () => sdk.Backups.ofVolumes('main')
)
```
## Packages
| Package | npm | Description |
|---------|-----|-------------|
| `package/` | `@start9labs/start-sdk` | Full SDK for service developers |
| `base/` | `@start9labs/start-sdk-base` | Core types, ABI definitions, and effects interface |
## Documentation
For comprehensive packaging guides, tutorials, and API reference:
**[docs.start9.com/packaging](https://docs.start9.com/packaging)**
The packaging docs cover:
- Environment setup and prerequisites
- Project structure and conventions
- Manifest, main, interfaces, actions, and all other service modules
- File models and configuration management
- Versioning, migrations, and initialization
- Dependencies and cross-service communication
- Building and installing `.s9pk` packages
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md) for environment setup, building from source, testing, and development workflow.
## Architecture
See [ARCHITECTURE.md](ARCHITECTURE.md) for a detailed overview of the SDK's internal structure, module responsibilities, and data flow.
## License
MIT
+169
View File
@@ -0,0 +1,169 @@
import { ActionId, ActionInput, ActionMetadata, SetMainStatus, DependencyRequirement, CheckDependenciesResult, SetHealth, BindParams, HostId, NetInfo, Host, ExportServiceInterfaceParams, ServiceInterface, CreateTaskParams, MountParams, StatusInfo, Manifest } from './osBindings';
import { PackageId, Dependencies, ServiceInterfaceId, SmtpValue, ActionResult, PluginHostnameInfo } from './types';
/** Used to reach out from the pure js runtime */
export type Effects = {
readonly eventId: string | null;
child: (name: string) => Effects;
constRetry?: () => void;
isInContext: boolean;
onLeaveContext: (fn: () => void | null | undefined) => void;
clearCallbacks: (options: {
only: number[];
} | {
except: number[];
}) => Promise<null>;
action: {
/** Define an action that can be invoked by a user or service */
export(options: {
id: ActionId;
metadata: ActionMetadata;
}): Promise<null>;
/** Remove all exported actions */
clear(options: {
except: ActionId[];
}): Promise<null>;
getInput(options: {
packageId?: PackageId;
actionId: ActionId;
}): Promise<ActionInput | null>;
run<Input extends Record<string, unknown>>(options: {
packageId?: PackageId;
actionId: ActionId;
input?: Input;
}): Promise<ActionResult | null>;
createTask(options: CreateTaskParams): Promise<null>;
clearTasks(options: {
only: string[];
} | {
except: string[];
}): Promise<null>;
};
/** restart this service's main function */
restart(): Promise<null>;
/** stop this service's main function */
shutdown(): Promise<null>;
/** ask the host os what the service's current status is */
getStatus(options: {
packageId?: PackageId;
callback?: () => void;
}): Promise<StatusInfo | null>;
/** DEPRECATED: indicate to the host os what runstate the service is in */
setMainStatus(options: SetMainStatus): Promise<null>;
/** Set the dependencies of what the service needs, usually run during the inputSpec action as a best practice */
setDependencies(options: {
dependencies: Dependencies;
}): Promise<null>;
/** Get the list of the dependencies, both the dynamic set by the effect of setDependencies and the end result any required in the manifest */
getDependencies(): Promise<DependencyRequirement[]>;
/** Test whether current dependency requirements are satisfied */
checkDependencies(options: {
packageIds?: PackageId[];
}): Promise<CheckDependenciesResult[]>;
/** mount a volume of a dependency */
mount(options: MountParams): Promise<string>;
/** Returns a list of the ids of all installed packages */
getInstalledPackages(): Promise<string[]>;
/** Returns the manifest of a service */
getServiceManifest(options: {
packageId: PackageId;
callback?: () => void;
}): Promise<Manifest>;
/** sets the result of a health check */
setHealth(o: SetHealth): Promise<null>;
subcontainer: {
/** A low level api used by SubContainer */
createFs(options: {
imageId: string;
name: string | null;
}): Promise<[string, string]>;
/** A low level api used by SubContainer */
destroyFs(options: {
guid: string;
}): Promise<null>;
};
/** Creates a host connected to the specified port with the provided options */
bind(options: BindParams): Promise<null>;
/** Get the port address for a service */
getServicePortForward(options: {
packageId?: PackageId;
hostId: HostId;
internalPort: number;
}): Promise<NetInfo>;
/** Removes all network bindings, called in the setupInputSpec */
clearBindings(options: {
except: {
id: HostId;
internalPort: number;
}[];
}): Promise<null>;
/** Returns information about the specified host, if it exists */
getHostInfo(options: {
packageId?: PackageId;
hostId: HostId;
callback?: () => void;
}): Promise<Host | null>;
/** Returns the IP address of the container */
getContainerIp(options: {
packageId?: PackageId;
callback?: () => void;
}): Promise<string>;
/** Returns the IP address of StartOS */
getOsIp(): Promise<string>;
/** Returns the effective outbound gateway for this service */
getOutboundGateway(options: {
callback?: () => void;
}): Promise<string>;
/** Creates an interface bound to a specific host and port to show to the user */
exportServiceInterface(options: ExportServiceInterfaceParams): Promise<null>;
/** Returns an exported service interface */
getServiceInterface(options: {
packageId?: PackageId;
serviceInterfaceId: ServiceInterfaceId;
callback?: () => void;
}): Promise<ServiceInterface | null>;
/** Returns all exported service interfaces for a package */
listServiceInterfaces(options: {
packageId?: PackageId;
callback?: () => void;
}): Promise<Record<ServiceInterfaceId, ServiceInterface>>;
/** Removes all service interfaces */
clearServiceInterfaces(options: {
except: ServiceInterfaceId[];
}): Promise<null>;
plugin: {
url: {
register(options: {
tableAction: ActionId;
}): Promise<null>;
exportUrl(options: {
hostnameInfo: PluginHostnameInfo;
removeAction: ActionId | null;
overflowActions: ActionId[];
}): Promise<null>;
clearUrls(options: {
except: PluginHostnameInfo[];
}): Promise<null>;
};
};
/** Returns a PEM encoded fullchain for the hostnames specified */
getSslCertificate: (options: {
hostnames: string[];
algorithm?: 'ecdsa' | 'ed25519';
callback?: () => void;
}) => Promise<[string, string, string]>;
/** Returns a PEM encoded private key corresponding to the certificate for the hostnames specified */
getSslKey: (options: {
hostnames: string[];
algorithm?: 'ecdsa' | 'ed25519';
}) => Promise<string>;
/** sets the version that this service's data has been migrated to */
setDataVersion(options: {
version: string | null;
}): Promise<null>;
/** returns the version that this service's data has been migrated to */
getDataVersion(): Promise<string | null>;
/** Returns globally configured SMTP settings, if they exist */
getSystemSmtp(options: {
callback?: () => void;
}): Promise<SmtpValue | null>;
};
+3
View File
@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=Effects.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"Effects.js","sourceRoot":"","sources":["../../../base/lib/Effects.ts"],"names":[],"mappings":""}
+40
View File
@@ -0,0 +1,40 @@
import * as T from '../types';
import * as IST from '../actions/input/inputSpecTypes';
import { Action, ActionInfo } from './setupActions';
export type RunActionInput<Input> = Input | ((prev?: {
spec: IST.InputSpec;
value: Input | null;
}) => Input);
export declare const runAction: <Input extends Record<string, unknown>>(options: {
effects: T.Effects;
actionId: T.ActionId;
input?: RunActionInput<Input>;
}) => Promise<T.ActionResult | null>;
type GetActionInputType<A extends ActionInfo<T.ActionId, any>> = A extends Action<T.ActionId, infer I> ? I : never;
type TaskBase = {
reason?: string;
replayId?: string;
};
type TaskInput<T extends ActionInfo<T.ActionId, any>> = {
kind: 'partial';
value: T.DeepPartial<GetActionInputType<T>>;
};
export type TaskOptions<T extends ActionInfo<T.ActionId, any>> = TaskBase & ({
when?: Exclude<T.TaskTrigger, {
condition: 'input-not-matches';
}>;
input?: TaskInput<T>;
} | {
when: T.TaskTrigger & {
condition: 'input-not-matches';
};
input: TaskInput<T>;
});
export declare const createTask: <T extends ActionInfo<T.ActionId, any>>(options: {
effects: T.Effects;
packageId: T.PackageId;
action: T;
severity: T.TaskSeverity;
options?: TaskOptions<T>;
}) => Promise<null>;
export {};
+70
View File
@@ -0,0 +1,70 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createTask = exports.runAction = void 0;
const runAction = async (options) => {
if (options.input) {
if (options.input instanceof Function) {
const prev = await options.effects.action.getInput({
// packageId: options.packageId,
actionId: options.actionId,
});
const input = options.input(prev
? { spec: prev.spec, value: prev.value }
: undefined);
return options.effects.action.run({
// packageId: options.packageId,
actionId: options.actionId,
input,
});
}
else {
return options.effects.action.run({
// packageId: options.packageId,
actionId: options.actionId,
input: options.input,
});
}
}
else {
return options.effects.action.run({
// packageId: options.packageId,
actionId: options.actionId,
});
}
};
exports.runAction = runAction;
const _validate = {};
/** Recursively converts undefined values to null so they survive JSON serialization */
function undefinedToNull(obj) {
if (obj === undefined)
return null;
if (obj === null || typeof obj !== 'object')
return obj;
if (Array.isArray(obj))
return obj.map(undefinedToNull);
const result = {};
for (const [k, v] of Object.entries(obj)) {
result[k] = undefinedToNull(v);
}
return result;
}
const createTask = (options) => {
const request = options.options || {};
const actionId = options.action.id;
const input = 'input' in request && request.input
? { ...request.input, value: undefinedToNull(request.input.value) }
: request.input;
const req = {
...request,
input,
actionId,
packageId: options.packageId,
action: undefined,
severity: options.severity,
replayId: request.replayId || `${options.packageId}:${actionId}`,
};
delete req.action;
return options.effects.action.createTask(req);
};
exports.createTask = createTask;
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../base/lib/actions/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
import { InputSpec } from './inputSpec';
import { List } from './list';
import { Value } from './value';
import { Variants } from './variants';
export { InputSpec as InputSpec, List, Value, Variants };
@@ -0,0 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Variants = exports.Value = exports.List = exports.InputSpec = void 0;
const inputSpec_1 = require("./inputSpec");
Object.defineProperty(exports, "InputSpec", { enumerable: true, get: function () { return inputSpec_1.InputSpec; } });
const list_1 = require("./list");
Object.defineProperty(exports, "List", { enumerable: true, get: function () { return list_1.List; } });
const value_1 = require("./value");
Object.defineProperty(exports, "Value", { enumerable: true, get: function () { return value_1.Value; } });
const variants_1 = require("./variants");
Object.defineProperty(exports, "Variants", { enumerable: true, get: function () { return variants_1.Variants; } });
//# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../base/lib/actions/input/builder/index.ts"],"names":[],"mappings":";;;AAAA,2CAAuC;AAKjB,0FALb,qBAAS,OAKa;AAJ/B,iCAA6B;AAII,qFAJxB,WAAI,OAIwB;AAHrC,mCAA+B;AAGQ,sFAH9B,aAAK,OAG8B;AAF5C,yCAAqC;AAES,yFAFrC,mBAAQ,OAEqC"}
@@ -0,0 +1,222 @@
import { ValueSpec } from '../inputSpecTypes';
import { Value } from './value';
import { Effects } from '../../../Effects';
import { z } from 'zod';
import { DeepPartial } from '../../../types';
import { InputSpecTools } from './inputSpecTools';
/** Options passed to a lazy builder function when resolving dynamic form field values. */
export type LazyBuildOptions<Type> = {
/** The effects interface for runtime operations (e.g. reading files, querying state). */
effects: Effects;
/** Previously saved form data to pre-fill the form with, or `null` for fresh creation. */
prefill: DeepPartial<Type> | null;
};
/**
* A function that lazily produces a value, potentially using effects and prefill data.
* Used by `dynamic*` variants of {@link Value} to compute form field options at runtime.
*/
export type LazyBuild<ExpectedOut, Type> = (options: LazyBuildOptions<Type>) => Promise<ExpectedOut> | ExpectedOut;
/**
* Defines which keys to keep when filtering an InputSpec.
* Use `true` to keep a field as-is, or a nested object to filter sub-fields of an object-typed field.
*/
export type FilterKeys<F> = {
[K in keyof F]?: F[K] extends Record<string, any> ? boolean | FilterKeys<F[K]> : boolean;
};
type RetainKey<T, F, Default extends boolean> = {
[K in keyof T]: K extends keyof F ? F[K] extends false ? never : K : Default extends true ? K : never;
}[keyof T];
/**
* Computes the resulting type after applying a {@link FilterKeys} shape to a type.
*/
export type ApplyFilter<T, F, Default extends boolean = false> = {
[K in RetainKey<T, F, Default>]: K extends keyof F ? true extends F[K] ? F[K] extends true ? T[K] : T[K] | undefined : T[K] extends Record<string, any> ? F[K] extends FilterKeys<T[K]> ? ApplyFilter<T[K], F[K]> : undefined : undefined : Default extends true ? T[K] : undefined;
};
/**
* Computes the union of all valid key-path tuples through a nested type.
* Each tuple represents a path from root to a field, recursing into object-typed sub-fields.
*/
export type KeyPaths<T> = {
[K in keyof T & string]: T[K] extends any[] ? [K] : T[K] extends Record<string, any> ? [K] | [K, ...KeyPaths<T[K]>] : [K];
}[keyof T & string];
/** Extracts the runtime type from an {@link InputSpec}. */
export type ExtractInputSpecType<A extends InputSpec<Record<string, any>, any>> = A extends InputSpec<infer B, any> ? B : never;
/** Extracts the static validation type from an {@link InputSpec}. */
export type ExtractInputSpecStaticValidatedAs<A extends InputSpec<any, Record<string, any>>> = A extends InputSpec<any, infer B> ? B : never;
/** Maps an object type to a record of {@link Value} entries for use with `InputSpec.of`. */
export type InputSpecOf<A extends Record<string, any>> = {
[K in keyof A]: Value<A[K]>;
};
/** A value that is either directly provided or lazily computed via a {@link LazyBuild} function. */
export type MaybeLazyValues<A, T> = LazyBuild<A, T> | A;
/**
* InputSpecs are the specs that are used by the os input specification form for this service.
* Here is an example of a simple input specification
```ts
const smallInputSpec = InputSpec.of({
test: Value.boolean({
name: "Test",
description: "This is the description for the test",
warning: null,
default: false,
}),
});
```
The idea of an inputSpec is that now the form is going to ask for
Test: [ ] and the value is going to be checked as a boolean.
There are more complex values like selects, lists, and objects. See {@link Value}
Also, there is the ability to get a validator/parser from this inputSpec spec.
```ts
const matchSmallInputSpec = smallInputSpec.validator();
type SmallInputSpec = typeof matchSmallInputSpec._TYPE;
```
Here is an example of a more complex input specification which came from an input specification for a service
that works with bitcoin, like c-lightning.
```ts
export const hostname = Value.string({
name: "Hostname",
default: null,
description: "Domain or IP address of bitcoin peer",
warning: null,
required: true,
masked: false,
placeholder: null,
pattern:
"(^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$)|((^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$)|(^[a-z2-7]{16}\\.onion$)|(^([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$))",
patternDescription:
"Must be either a domain name, or an IPv4 or IPv6 address. Do not include protocol scheme (eg 'http://') or port.",
});
export const port = Value.number({
name: "Port",
default: null,
description: "Port that peer is listening on for inbound p2p connections",
warning: null,
required: false,
range: "[0,65535]",
integral: true,
units: null,
placeholder: null,
});
export const addNodesSpec = InputSpec.of({ hostname: hostname, port: port });
```
*/
export declare class InputSpec<Type extends StaticValidatedAs, StaticValidatedAs extends Record<string, any> = Type> {
private readonly spec;
readonly validator: z.ZodType<StaticValidatedAs>;
private constructor();
_TYPE: Type;
_PARTIAL: DeepPartial<Type>;
readonly partialValidator: z.ZodType<DeepPartial<StaticValidatedAs>>;
/**
* Builds the runtime form specification and combined Zod validator from this InputSpec's fields.
*
* @returns An object containing the resolved `spec` (field specs keyed by name) and a combined `validator`
*/
build<OuterType>(options: LazyBuildOptions<OuterType>): Promise<{
spec: {
[K in keyof Type]: ValueSpec;
};
validator: z.ZodType<Type>;
}>;
/**
* Adds multiple fields to this spec at once, returning a new `InputSpec` with extended types.
*
* @param build - A record of {@link Value} entries, or a function receiving typed tools that returns one
*/
add<AddSpec extends Record<string, Value<any, any, any>>>(build: AddSpec | ((tools: InputSpecTools<Type>) => AddSpec)): InputSpec<Type & {
[K in keyof AddSpec]: AddSpec[K] extends Value<infer T, any, any> ? T : never;
}, StaticValidatedAs & {
[K in keyof AddSpec]: AddSpec[K] extends Value<any, infer S, any> ? S : never;
}>;
/**
* Returns a new InputSpec containing only the specified keys.
* Use `true` to keep a field as-is, or a nested object to filter sub-fields of object-typed fields.
*
* @example
* ```ts
* const full = InputSpec.of({
* name: Value.text({ name: 'Name', required: true, default: null }),
* settings: Value.object({ name: 'Settings' }, InputSpec.of({
* debug: Value.toggle({ name: 'Debug', default: false }),
* port: Value.number({ name: 'Port', required: true, default: 8080, integer: true }),
* })),
* })
* const filtered = full.filter({ name: true, settings: { debug: true } })
* ```
*/
filter<F extends FilterKeys<Type>, Default extends boolean = false>(keys: F, keepByDefault?: Default): InputSpec<ApplyFilter<Type, F, Default> & ApplyFilter<StaticValidatedAs, F, Default>, ApplyFilter<StaticValidatedAs, F, Default>>;
/**
* Returns a new InputSpec with the specified keys disabled.
* Use `true` to disable a field, or a nested object to disable sub-fields of object-typed fields.
* All fields remain in the spec — disabled fields simply cannot be edited by the user.
*
* @param keys - Which fields to disable, using the same shape as {@link FilterKeys}
* @param message - The reason the fields are disabled, displayed to the user
*
* @example
* ```ts
* const spec = InputSpec.of({
* name: Value.text({ name: 'Name', required: true, default: null }),
* settings: Value.object({ name: 'Settings' }, InputSpec.of({
* debug: Value.toggle({ name: 'Debug', default: false }),
* port: Value.number({ name: 'Port', required: true, default: 8080, integer: true }),
* })),
* })
* const disabled = spec.disable({ name: true, settings: { debug: true } }, 'Managed by the system')
* ```
*/
disable(keys: FilterKeys<Type>, message: string): InputSpec<Type, StaticValidatedAs>;
/**
* Resolves a key path to its corresponding display name path.
* Each key is mapped to the `name` property of its built {@link ValueSpec}.
* Recurses into `Value.object` sub-specs for nested paths.
*
* @param path - Typed tuple of field keys (e.g. `["settings", "debug"]`)
* @param options - Build options providing effects and prefill data
* @returns Array of display names (e.g. `["Settings", "Debug"]`)
*/
namePath<OuterType>(path: KeyPaths<Type>, options: LazyBuildOptions<OuterType>): Promise<string[]>;
/**
* Resolves a key path to the description of the target field.
* Recurses into `Value.object` sub-specs for nested paths.
*
* @param path - Typed tuple of field keys (e.g. `["settings", "debug"]`)
* @param options - Build options providing effects and prefill data
* @returns The description string, or `null` if the field has no description or was not found
*/
description<OuterType>(path: KeyPaths<Type>, options: LazyBuildOptions<OuterType>): Promise<string | null>;
/**
* Returns a new InputSpec filtered to only include keys present in the given partial object.
* For nested `Value.object` fields, recurses into the partial value to filter sub-fields.
*
* @param partial - A deep-partial object whose defined keys determine which fields to keep
*/
filterFromPartial(partial: DeepPartial<Type>): InputSpec<DeepPartial<Type> & DeepPartial<StaticValidatedAs>, DeepPartial<StaticValidatedAs>>;
/**
* Returns a new InputSpec with fields disabled based on which keys are present in the given partial object.
* For nested `Value.object` fields, recurses into the partial value to disable sub-fields.
* All fields remain in the spec — disabled fields simply cannot be edited by the user.
*
* @param partial - A deep-partial object whose defined keys determine which fields to disable
* @param message - The reason the fields are disabled, displayed to the user
*/
disableFromPartial(partial: DeepPartial<Type>, message: string): InputSpec<Type, StaticValidatedAs>;
/**
* Creates an `InputSpec` from a plain record of {@link Value} entries.
*
* @example
* ```ts
* const spec = InputSpec.of({
* username: Value.text({ name: 'Username', required: true, default: null }),
* verbose: Value.toggle({ name: 'Verbose Logging', default: false }),
* })
* ```
*/
static of<Spec extends Record<string, Value<any, any>>>(spec: Spec): InputSpec<{ [K in keyof Spec]: Spec[K] extends Value<infer T extends any, any, unknown> ? T : never; }, { [K_1 in keyof Spec]: Spec[K_1] extends Value<any, infer T_1, unknown> ? T_1 : never; }>;
}
export {};
@@ -0,0 +1,319 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.InputSpec = void 0;
const value_1 = require("./value");
const zod_1 = require("zod");
const zod_deep_partial_1 = require("zod-deep-partial");
const inputSpecTools_1 = require("./inputSpecTools");
/**
* InputSpecs are the specs that are used by the os input specification form for this service.
* Here is an example of a simple input specification
```ts
const smallInputSpec = InputSpec.of({
test: Value.boolean({
name: "Test",
description: "This is the description for the test",
warning: null,
default: false,
}),
});
```
The idea of an inputSpec is that now the form is going to ask for
Test: [ ] and the value is going to be checked as a boolean.
There are more complex values like selects, lists, and objects. See {@link Value}
Also, there is the ability to get a validator/parser from this inputSpec spec.
```ts
const matchSmallInputSpec = smallInputSpec.validator();
type SmallInputSpec = typeof matchSmallInputSpec._TYPE;
```
Here is an example of a more complex input specification which came from an input specification for a service
that works with bitcoin, like c-lightning.
```ts
export const hostname = Value.string({
name: "Hostname",
default: null,
description: "Domain or IP address of bitcoin peer",
warning: null,
required: true,
masked: false,
placeholder: null,
pattern:
"(^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$)|((^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$)|(^[a-z2-7]{16}\\.onion$)|(^([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$))",
patternDescription:
"Must be either a domain name, or an IPv4 or IPv6 address. Do not include protocol scheme (eg 'http://') or port.",
});
export const port = Value.number({
name: "Port",
default: null,
description: "Port that peer is listening on for inbound p2p connections",
warning: null,
required: false,
range: "[0,65535]",
integral: true,
units: null,
placeholder: null,
});
export const addNodesSpec = InputSpec.of({ hostname: hostname, port: port });
```
*/
class InputSpec {
constructor(spec, validator) {
this.spec = spec;
this.validator = validator;
this._TYPE = null;
this._PARTIAL = null;
this.partialValidator = (0, zod_deep_partial_1.zodDeepPartial)(this.validator);
}
/**
* Builds the runtime form specification and combined Zod validator from this InputSpec's fields.
*
* @returns An object containing the resolved `spec` (field specs keyed by name) and a combined `validator`
*/
async build(options) {
const answer = {};
const validator = {};
for (const k in this.spec) {
const built = await this.spec[k].build(options);
answer[k] = built.spec;
validator[k] = built.validator;
}
return {
spec: answer,
validator: zod_1.z.object(validator),
};
}
/**
* Adds multiple fields to this spec at once, returning a new `InputSpec` with extended types.
*
* @param build - A record of {@link Value} entries, or a function receiving typed tools that returns one
*/
add(build) {
const addedValues = build instanceof Function ? build((0, inputSpecTools_1.createInputSpecTools)()) : build;
const newSpec = { ...this.spec, ...addedValues };
const newValidator = zod_1.z.object(Object.fromEntries(Object.entries(newSpec).map(([k, v]) => [
k,
v.validator,
])));
return new InputSpec(newSpec, newValidator);
}
/**
* Returns a new InputSpec containing only the specified keys.
* Use `true` to keep a field as-is, or a nested object to filter sub-fields of object-typed fields.
*
* @example
* ```ts
* const full = InputSpec.of({
* name: Value.text({ name: 'Name', required: true, default: null }),
* settings: Value.object({ name: 'Settings' }, InputSpec.of({
* debug: Value.toggle({ name: 'Debug', default: false }),
* port: Value.number({ name: 'Port', required: true, default: 8080, integer: true }),
* })),
* })
* const filtered = full.filter({ name: true, settings: { debug: true } })
* ```
*/
filter(keys, keepByDefault) {
const newSpec = {};
for (const k of Object.keys(this.spec)) {
const filterVal = keys[k];
const value = this.spec[k];
if (!value)
continue;
if (filterVal === true) {
newSpec[k] = value;
}
else if (typeof filterVal === 'object' && filterVal !== null) {
const objectMeta = value._objectSpec;
if (objectMeta) {
const filteredInner = objectMeta.inputSpec.filter(filterVal, keepByDefault);
newSpec[k] = value_1.Value.object(objectMeta.params, filteredInner);
}
else {
newSpec[k] = value;
}
}
else if (keepByDefault && filterVal !== false) {
newSpec[k] = value;
}
}
const newValidator = zod_1.z.object(Object.fromEntries(Object.entries(newSpec).map(([k, v]) => [k, v.validator])));
return new InputSpec(newSpec, newValidator);
}
/**
* Returns a new InputSpec with the specified keys disabled.
* Use `true` to disable a field, or a nested object to disable sub-fields of object-typed fields.
* All fields remain in the spec — disabled fields simply cannot be edited by the user.
*
* @param keys - Which fields to disable, using the same shape as {@link FilterKeys}
* @param message - The reason the fields are disabled, displayed to the user
*
* @example
* ```ts
* const spec = InputSpec.of({
* name: Value.text({ name: 'Name', required: true, default: null }),
* settings: Value.object({ name: 'Settings' }, InputSpec.of({
* debug: Value.toggle({ name: 'Debug', default: false }),
* port: Value.number({ name: 'Port', required: true, default: 8080, integer: true }),
* })),
* })
* const disabled = spec.disable({ name: true, settings: { debug: true } }, 'Managed by the system')
* ```
*/
disable(keys, message) {
const newSpec = {};
for (const k in this.spec) {
const filterVal = keys[k];
const value = this.spec[k];
if (!filterVal) {
newSpec[k] = value;
}
else if (filterVal === true) {
newSpec[k] = value.withDisabled(message);
}
else if (typeof filterVal === 'object' && filterVal !== null) {
const objectMeta = value._objectSpec;
if (objectMeta) {
const disabledInner = objectMeta.inputSpec.disable(filterVal, message);
newSpec[k] = value_1.Value.object(objectMeta.params, disabledInner);
}
else {
newSpec[k] = value.withDisabled(message);
}
}
}
const newValidator = zod_1.z.object(Object.fromEntries(Object.entries(newSpec).map(([k, v]) => [k, v.validator])));
return new InputSpec(newSpec, newValidator);
}
/**
* Resolves a key path to its corresponding display name path.
* Each key is mapped to the `name` property of its built {@link ValueSpec}.
* Recurses into `Value.object` sub-specs for nested paths.
*
* @param path - Typed tuple of field keys (e.g. `["settings", "debug"]`)
* @param options - Build options providing effects and prefill data
* @returns Array of display names (e.g. `["Settings", "Debug"]`)
*/
async namePath(path, options) {
if (path.length === 0)
return [];
const [key, ...rest] = path;
const value = this.spec[key];
if (!value)
return [];
const built = await value.build(options);
const name = 'name' in built.spec ? built.spec.name : key;
if (rest.length === 0)
return [name];
const objectMeta = value._objectSpec;
if (objectMeta) {
const innerNames = await objectMeta.inputSpec.namePath(rest, options);
return [name, ...innerNames];
}
return [name];
}
/**
* Resolves a key path to the description of the target field.
* Recurses into `Value.object` sub-specs for nested paths.
*
* @param path - Typed tuple of field keys (e.g. `["settings", "debug"]`)
* @param options - Build options providing effects and prefill data
* @returns The description string, or `null` if the field has no description or was not found
*/
async description(path, options) {
if (path.length === 0)
return null;
const [key, ...rest] = path;
const value = this.spec[key];
if (!value)
return null;
if (rest.length === 0) {
const built = await value.build(options);
return 'description' in built.spec
? built.spec.description
: null;
}
const objectMeta = value._objectSpec;
if (objectMeta) {
return objectMeta.inputSpec.description(rest, options);
}
return null;
}
/**
* Returns a new InputSpec filtered to only include keys present in the given partial object.
* For nested `Value.object` fields, recurses into the partial value to filter sub-fields.
*
* @param partial - A deep-partial object whose defined keys determine which fields to keep
*/
filterFromPartial(partial) {
const newSpec = {};
for (const k of Object.keys(partial)) {
const value = this.spec[k];
if (!value)
continue;
const objectMeta = value._objectSpec;
if (objectMeta) {
const partialVal = partial[k];
if (typeof partialVal === 'object' && partialVal !== null) {
const filteredInner = objectMeta.inputSpec.filterFromPartial(partialVal);
newSpec[k] = value_1.Value.object(objectMeta.params, filteredInner);
continue;
}
}
newSpec[k] = value;
}
const newValidator = zod_1.z.object(Object.fromEntries(Object.entries(newSpec).map(([k, v]) => [k, v.validator])));
return new InputSpec(newSpec, newValidator);
}
/**
* Returns a new InputSpec with fields disabled based on which keys are present in the given partial object.
* For nested `Value.object` fields, recurses into the partial value to disable sub-fields.
* All fields remain in the spec — disabled fields simply cannot be edited by the user.
*
* @param partial - A deep-partial object whose defined keys determine which fields to disable
* @param message - The reason the fields are disabled, displayed to the user
*/
disableFromPartial(partial, message) {
const newSpec = {};
for (const k in this.spec) {
const value = this.spec[k];
if (!(k in partial)) {
newSpec[k] = value;
continue;
}
const objectMeta = value._objectSpec;
if (objectMeta) {
const partialVal = partial[k];
if (typeof partialVal === 'object' && partialVal !== null) {
const disabledInner = objectMeta.inputSpec.disableFromPartial(partialVal, message);
newSpec[k] = value_1.Value.object(objectMeta.params, disabledInner);
continue;
}
}
newSpec[k] = value.withDisabled(message);
}
const newValidator = zod_1.z.object(Object.fromEntries(Object.entries(newSpec).map(([k, v]) => [k, v.validator])));
return new InputSpec(newSpec, newValidator);
}
/**
* Creates an `InputSpec` from a plain record of {@link Value} entries.
*
* @example
* ```ts
* const spec = InputSpec.of({
* username: Value.text({ name: 'Username', required: true, default: null }),
* verbose: Value.toggle({ name: 'Verbose Logging', default: false }),
* })
* ```
*/
static of(spec) {
const validator = zod_1.z.object(Object.fromEntries(Object.entries(spec).map(([k, v]) => [k, v.validator])));
return new InputSpec(spec, validator);
}
}
exports.InputSpec = InputSpec;
//# sourceMappingURL=inputSpec.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,173 @@
import { InputSpec, LazyBuild } from './inputSpec';
import { AsRequired, FileInfo, Value } from './value';
import { List } from './list';
import { UnionRes, UnionResStaticValidatedAs, Variants } from './variants';
import { Pattern, RandomString, ValueSpecDatetime, ValueSpecText } from '../inputSpecTypes';
import { DefaultString } from '../inputSpecTypes';
import { z } from 'zod';
import { ListValueSpecText } from '../inputSpecTypes';
export interface InputSpecTools<OuterType> {
Value: BoundValue<OuterType>;
Variants: typeof Variants;
InputSpec: typeof InputSpec;
List: BoundList<OuterType>;
}
export interface BoundValue<OuterType> {
toggle: typeof Value.toggle;
text: typeof Value.text;
textarea: typeof Value.textarea;
number: typeof Value.number;
color: typeof Value.color;
datetime: typeof Value.datetime;
select: typeof Value.select;
multiselect: typeof Value.multiselect;
object: typeof Value.object;
file: typeof Value.file;
list: typeof Value.list;
hidden: typeof Value.hidden;
union: typeof Value.union;
dynamicToggle(a: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
default: boolean;
disabled?: false | string;
}, OuterType>): Value<boolean, boolean, OuterType>;
dynamicText<Required extends boolean>(getA: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
default: DefaultString | null;
required: Required;
masked?: boolean;
placeholder?: string | null;
minLength?: number | null;
maxLength?: number | null;
patterns?: Pattern[];
inputmode?: ValueSpecText['inputmode'];
disabled?: string | false;
generate?: null | RandomString;
}, OuterType>): Value<AsRequired<string, Required>, string | null, OuterType>;
dynamicTextarea<Required extends boolean>(getA: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
default: string | null;
required: Required;
minLength?: number | null;
maxLength?: number | null;
patterns?: Pattern[];
minRows?: number;
maxRows?: number;
placeholder?: string | null;
disabled?: false | string;
}, OuterType>): Value<AsRequired<string, Required>, string | null, OuterType>;
dynamicNumber<Required extends boolean>(getA: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
default: number | null;
required: Required;
min?: number | null;
max?: number | null;
step?: number | null;
integer: boolean;
units?: string | null;
placeholder?: string | null;
disabled?: false | string;
}, OuterType>): Value<AsRequired<number, Required>, number | null, OuterType>;
dynamicColor<Required extends boolean>(getA: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
default: string | null;
required: Required;
disabled?: false | string;
}, OuterType>): Value<AsRequired<string, Required>, string | null, OuterType>;
dynamicDatetime<Required extends boolean>(getA: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
default: string | null;
required: Required;
inputmode?: ValueSpecDatetime['inputmode'];
min?: string | null;
max?: string | null;
disabled?: false | string;
}, OuterType>): Value<AsRequired<string, Required>, string | null, OuterType>;
dynamicSelect<Values extends Record<string, string>>(getA: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
default: string;
values: Values;
disabled?: false | string | string[];
}, OuterType>): Value<keyof Values & string, keyof Values & string, OuterType>;
dynamicMultiselect<Values extends Record<string, string>>(getA: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
default: string[];
values: Values;
minLength?: number | null;
maxLength?: number | null;
disabled?: false | string | string[];
}, OuterType>): Value<(keyof Values & string)[], (keyof Values & string)[], OuterType>;
dynamicFile<Required extends boolean>(a: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
extensions: string[];
required: Required;
}, OuterType>): Value<AsRequired<FileInfo, Required>, FileInfo | null, OuterType>;
dynamicUnion<VariantValues extends {
[K in string]: {
name: string;
spec: InputSpec<any>;
};
}>(getA: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
variants: Variants<VariantValues>;
default: keyof VariantValues & string;
disabled: string[] | false | string;
}, OuterType>): Value<UnionRes<VariantValues>, UnionRes<VariantValues>, OuterType>;
dynamicUnion<StaticVariantValues extends {
[K in string]: {
name: string;
spec: InputSpec<any, any>;
};
}, VariantValues extends StaticVariantValues>(getA: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
variants: Variants<VariantValues>;
default: keyof VariantValues & string;
disabled: string[] | false | string;
}, OuterType>, validator: z.ZodType<UnionResStaticValidatedAs<StaticVariantValues>>): Value<UnionRes<VariantValues>, UnionResStaticValidatedAs<StaticVariantValues>, OuterType>;
dynamicHidden<T>(getParser: LazyBuild<z.ZodType<T>, OuterType>): Value<T, T, OuterType>;
}
export interface BoundList<OuterType> {
text: typeof List.text;
obj: typeof List.obj;
dynamicText(getA: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
default?: string[];
minLength?: number | null;
maxLength?: number | null;
disabled?: false | string;
generate?: null | RandomString;
spec: {
masked?: boolean;
placeholder?: string | null;
minLength?: number | null;
maxLength?: number | null;
patterns?: Pattern[];
inputmode?: ListValueSpecText['inputmode'];
};
}, OuterType>): List<string[], string[], OuterType>;
}
export declare function createInputSpecTools<OuterType>(): InputSpecTools<OuterType>;
@@ -0,0 +1,16 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createInputSpecTools = createInputSpecTools;
const inputSpec_1 = require("./inputSpec");
const value_1 = require("./value");
const list_1 = require("./list");
const variants_1 = require("./variants");
function createInputSpecTools() {
return {
Value: value_1.Value,
Variants: variants_1.Variants,
InputSpec: inputSpec_1.InputSpec,
List: list_1.List,
};
}
//# sourceMappingURL=inputSpecTools.js.map
@@ -0,0 +1 @@
{"version":3,"file":"inputSpecTools.js","sourceRoot":"","sources":["../../../../../../base/lib/actions/input/builder/inputSpecTools.ts"],"names":[],"mappings":";;AA0QA,oDAOC;AAjRD,2CAAkD;AAClD,mCAAqD;AACrD,iCAA6B;AAC7B,yCAA0E;AAuQ1E,SAAgB,oBAAoB;IAClC,OAAO;QACL,KAAK,EAAE,aAAqC;QAC5C,QAAQ,EAAR,mBAAQ;QACR,SAAS,EAAT,qBAAS;QACT,IAAI,EAAE,WAAmC;KAC1C,CAAA;AACH,CAAC"}
@@ -0,0 +1,103 @@
import { InputSpec, LazyBuild } from './inputSpec';
import { ListValueSpecText, Pattern, RandomString, UniqueBy, ValueSpecList } from '../inputSpecTypes';
import { z } from 'zod';
/**
* Builder class for defining list-type form fields.
*
* A list presents an interface to add, remove, and reorder items. Items can be
* either text strings ({@link List.text}) or structured objects ({@link List.obj}).
*
* Used with {@link Value.list} to include a list field in an {@link InputSpec}.
*/
export declare class List<Type extends StaticValidatedAs, StaticValidatedAs = Type, OuterType = unknown> {
build: LazyBuild<{
spec: ValueSpecList;
validator: z.ZodType<Type>;
}, OuterType>;
readonly validator: z.ZodType<StaticValidatedAs>;
private constructor();
readonly _TYPE: Type;
/**
* Creates a list of text input items.
*
* @param a - List-level options (name, description, min/max length, defaults)
* @param aSpec - Item-level options (patterns, input mode, masking, generation)
*/
static text(a: {
name: string;
description?: string | null;
warning?: string | null;
default?: string[];
minLength?: number | null;
maxLength?: number | null;
}, aSpec: {
/**
* @description Mask (aka camouflage) text input with dots: ● ● ●
* @default false
*/
masked?: boolean;
placeholder?: string | null;
minLength?: number | null;
maxLength?: number | null;
/**
* @description A list of regular expressions to which the text must conform to pass validation. A human readable description is provided in case the validation fails.
* @default []
* @example
* ```
[
{
regex: "[a-z]",
description: "May only contain lower case letters from the English alphabet."
}
]
* ```
*/
patterns?: Pattern[];
/**
* @description Informs the browser how to behave and which keyboard to display on mobile
* @default "text"
*/
inputmode?: ListValueSpecText['inputmode'];
/**
* @description Displays a button that will generate a random string according to the provided charset and len attributes.
*/
generate?: null | RandomString;
}): List<string[], string[], unknown>;
/** Like {@link List.text} but options are resolved lazily at runtime via a builder function. */
static dynamicText<OuterType = unknown>(getA: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
default?: string[];
minLength?: number | null;
maxLength?: number | null;
disabled?: false | string;
generate?: null | RandomString;
spec: {
masked?: boolean;
placeholder?: string | null;
minLength?: number | null;
maxLength?: number | null;
patterns?: Pattern[];
inputmode?: ListValueSpecText['inputmode'];
};
}, OuterType>): List<string[], string[], OuterType>;
/**
* Creates a list of structured object items, each defined by a nested {@link InputSpec}.
*
* @param a - List-level options (name, description, min/max length)
* @param aSpec - Item-level options (the nested spec, display expression, uniqueness constraint)
*/
static obj<Type extends StaticValidatedAs, StaticValidatedAs extends Record<string, any>>(a: {
name: string;
description?: string | null;
warning?: string | null;
default?: [];
minLength?: number | null;
maxLength?: number | null;
}, aSpec: {
spec: InputSpec<Type, StaticValidatedAs>;
displayAs?: null | string;
uniqueBy?: null | UniqueBy;
}): List<Type[], StaticValidatedAs[], unknown>;
}
@@ -0,0 +1,121 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.List = void 0;
const zod_1 = require("zod");
/**
* Builder class for defining list-type form fields.
*
* A list presents an interface to add, remove, and reorder items. Items can be
* either text strings ({@link List.text}) or structured objects ({@link List.obj}).
*
* Used with {@link Value.list} to include a list field in an {@link InputSpec}.
*/
class List {
constructor(build, validator) {
this.build = build;
this.validator = validator;
this._TYPE = null;
}
/**
* Creates a list of text input items.
*
* @param a - List-level options (name, description, min/max length, defaults)
* @param aSpec - Item-level options (patterns, input mode, masking, generation)
*/
static text(a, aSpec) {
const validator = zod_1.z.array(zod_1.z.string());
return new List(() => {
const spec = {
type: 'text',
placeholder: null,
minLength: null,
maxLength: null,
masked: false,
inputmode: 'text',
generate: null,
patterns: aSpec.patterns || [],
...aSpec,
};
const built = {
description: null,
warning: null,
default: [],
type: 'list',
minLength: null,
maxLength: null,
disabled: false,
...a,
spec,
};
return { spec: built, validator };
}, validator);
}
/** Like {@link List.text} but options are resolved lazily at runtime via a builder function. */
static dynamicText(getA) {
const validator = zod_1.z.array(zod_1.z.string());
return new List(async (options) => {
const { spec: aSpec, ...a } = await getA(options);
const spec = {
type: 'text',
placeholder: null,
minLength: null,
maxLength: null,
masked: false,
inputmode: 'text',
generate: null,
patterns: aSpec.patterns || [],
...aSpec,
};
const built = {
description: null,
warning: null,
default: [],
type: 'list',
minLength: null,
maxLength: null,
disabled: false,
...a,
spec,
};
return { spec: built, validator };
}, validator);
}
/**
* Creates a list of structured object items, each defined by a nested {@link InputSpec}.
*
* @param a - List-level options (name, description, min/max length)
* @param aSpec - Item-level options (the nested spec, display expression, uniqueness constraint)
*/
static obj(a, aSpec) {
return new List(async (options) => {
const { spec: previousSpecSpec, ...restSpec } = aSpec;
const built = await previousSpecSpec.build(options);
const spec = {
type: 'object',
displayAs: null,
uniqueBy: null,
...restSpec,
spec: built.spec,
};
const value = {
spec,
default: [],
...a,
};
return {
spec: {
description: null,
warning: null,
minLength: null,
maxLength: null,
type: 'list',
disabled: false,
...value,
},
validator: zod_1.z.array(built.validator),
};
}, zod_1.z.array(aSpec.spec.validator));
}
}
exports.List = List;
//# sourceMappingURL=list.js.map
@@ -0,0 +1 @@
{"version":3,"file":"list.js","sourceRoot":"","sources":["../../../../../../base/lib/actions/input/builder/list.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,791 @@
import { InputSpec, LazyBuild } from './inputSpec';
import { List } from './list';
import { UnionRes, UnionResStaticValidatedAs, Variants } from './variants';
import { Pattern, RandomString, ValueSpec, ValueSpecDatetime, ValueSpecText } from '../inputSpecTypes';
import { DefaultString } from '../inputSpecTypes';
import { z } from 'zod';
import { DeepPartial } from '../../../types';
/** Zod schema for a file upload result — validates `{ path, commitment: { hash, size } }`. */
export declare const fileInfoParser: z.ZodObject<{
path: z.ZodString;
commitment: z.ZodObject<{
hash: z.ZodString;
size: z.ZodNumber;
}, z.core.$strip>;
}, z.core.$strip>;
/** The parsed result of a file upload, containing the file path and its content commitment (hash + size). */
export type FileInfo = z.infer<typeof fileInfoParser>;
/** Conditional type: returns `T` if `Required` is `true`, otherwise `T | null`. */
export type AsRequired<T, Required extends boolean> = Required extends true ? T : T | null;
/**
* Core builder class for defining a single form field in a service configuration spec.
*
* Each static factory method (e.g. `Value.text()`, `Value.toggle()`, `Value.select()`) creates
* a typed `Value` instance representing a specific field type. Dynamic variants (e.g. `Value.dynamicText()`)
* allow the field options to be computed lazily at runtime.
*
* Use with {@link InputSpec} to compose complete form specifications.
*
* @typeParam Type - The runtime type this field produces when filled in
* @typeParam StaticValidatedAs - The compile-time validated type (usually same as Type)
* @typeParam OuterType - The parent form's type context (used by dynamic variants)
*/
export declare class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type, OuterType = unknown> {
build: LazyBuild<{
spec: ValueSpec;
validator: z.ZodType<Type>;
}, OuterType>;
readonly validator: z.ZodType<StaticValidatedAs>;
protected constructor(build: LazyBuild<{
spec: ValueSpec;
validator: z.ZodType<Type>;
}, OuterType>, validator: z.ZodType<StaticValidatedAs>);
_TYPE: Type;
_PARTIAL: DeepPartial<Type>;
/** @internal Used by {@link InputSpec.filter} to support nested filtering of object-typed fields. */
_objectSpec?: {
inputSpec: InputSpec<any, any>;
params: {
name: string;
description?: string | null;
};
};
/**
* @description Displays a boolean toggle to enable/disable
* @example
* ```
toggleExample: Value.toggle({
// required
name: 'Toggle Example',
default: true,
// optional
description: null,
warning: null,
immutable: false,
}),
* ```
*/
static toggle(a: {
name: string;
description?: string | null;
/** Presents a warning prompt before permitting the value to change. */
warning?: string | null;
default: boolean;
/**
* @description Once set, the value can never be changed.
* @default false
*/
immutable?: boolean;
}): Value<boolean, boolean, unknown>;
/** Like {@link Value.toggle} but options are resolved lazily at runtime via a builder function. */
static dynamicToggle<OuterType = unknown>(a: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
default: boolean;
disabled?: false | string;
}, OuterType>): Value<boolean, boolean, OuterType>;
/**
* @description Displays a text input field
* @example
* ```
textExample: Value.text({
// required
name: 'Text Example',
required: false,
default: null,
// optional
description: null,
placeholder: null,
warning: null,
generate: null,
inputmode: 'text',
masked: false,
minLength: null,
maxLength: null,
patterns: [],
immutable: false,
}),
* ```
*/
static text<Required extends boolean>(a: {
name: string;
description?: string | null;
/** Presents a warning prompt before permitting the value to change. */
warning?: string | null;
/**
* provide a default value.
* @type { string | RandomString | null }
* @example default: null
* @example default: 'World'
* @example default: { charset: 'abcdefg', len: 16 }
*/
default: string | RandomString | null;
required: Required;
/**
* @description Mask (aka camouflage) text input with dots: ● ● ●
* @default false
*/
masked?: boolean;
placeholder?: string | null;
minLength?: number | null;
maxLength?: number | null;
/**
* @description A list of regular expressions to which the text must conform to pass validation. A human readable description is provided in case the validation fails.
* @default []
* @example
* ```
[
{
regex: "[a-z]",
description: "May only contain lower case letters from the English alphabet."
}
]
* ```
*/
patterns?: Pattern[];
/**
* @description Informs the browser how to behave and which keyboard to display on mobile
* @default "text"
*/
inputmode?: ValueSpecText['inputmode'];
/**
* @description Once set, the value can never be changed.
* @default false
*/
immutable?: boolean;
/**
* @description Displays a button that will generate a random string according to the provided charset and len attributes.
*/
generate?: RandomString | null;
}): Value<AsRequired<string, Required>, AsRequired<string, Required>, unknown>;
/** Like {@link Value.text} but options are resolved lazily at runtime via a builder function. */
static dynamicText<Required extends boolean, OuterType = unknown>(getA: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
default: DefaultString | null;
required: Required;
masked?: boolean;
placeholder?: string | null;
minLength?: number | null;
maxLength?: number | null;
patterns?: Pattern[];
inputmode?: ValueSpecText['inputmode'];
disabled?: string | false;
generate?: null | RandomString;
}, OuterType>): Value<AsRequired<string, Required>, string | null, OuterType>;
/**
* @description Displays a large textarea field for long form entry.
* @example
* ```
textareaExample: Value.textarea({
// required
name: 'Textarea Example',
required: false,
default: null,
// optional
description: null,
placeholder: null,
warning: null,
minLength: null,
maxLength: null,
minRows: 3
maxRows: 6
immutable: false,
}),
* ```
*/
static textarea<Required extends boolean>(a: {
name: string;
description?: string | null;
/** Presents a warning prompt before permitting the value to change. */
warning?: string | null;
default: string | null;
required: Required;
minLength?: number | null;
maxLength?: number | null;
/**
* @description A list of regular expressions to which the text must conform to pass validation. A human readable description is provided in case the validation fails.
* @default []
* @example
* ```
[
{
regex: "[a-z]",
description: "May only contain lower case letters from the English alphabet."
}
]
* ```
*/
patterns?: Pattern[];
/** Defaults to 3 */
minRows?: number;
/** Maximum number of rows before scroll appears. Defaults to 6 */
maxRows?: number;
placeholder?: string | null;
/**
* @description Once set, the value can never be changed.
* @default false
*/
immutable?: boolean;
}): Value<AsRequired<string, Required>, AsRequired<string, Required>, unknown>;
/** Like {@link Value.textarea} but options are resolved lazily at runtime via a builder function. */
static dynamicTextarea<Required extends boolean, OuterType = unknown>(getA: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
default: string | null;
required: Required;
minLength?: number | null;
maxLength?: number | null;
patterns?: Pattern[];
minRows?: number;
maxRows?: number;
placeholder?: string | null;
disabled?: false | string;
}, OuterType>): Value<AsRequired<string, Required>, string | null, OuterType>;
/**
* @description Displays a number input field
* @example
* ```
numberExample: Value.number({
// required
name: 'Number Example',
required: false,
default: null,
integer: true,
// optional
description: null,
placeholder: null,
warning: null,
min: null,
max: null,
immutable: false,
step: null,
units: null,
}),
* ```
*/
static number<Required extends boolean>(a: {
name: string;
description?: string | null;
/** Presents a warning prompt before permitting the value to change. */
warning?: string | null;
/**
* @description optionally provide a default value.
* @type { default: number | null }
* @example default: null
* @example default: 7
*/
default: number | null;
required: Required;
min?: number | null;
max?: number | null;
/**
* @description How much does the number increase/decrease when using the arrows provided by the browser.
* @default 1
*/
step?: number | null;
/**
* @description Requires the number to be an integer.
*/
integer: boolean;
/**
* @description Optionally display units to the right of the input box.
*/
units?: string | null;
placeholder?: string | null;
/**
* @description Once set, the value can never be changed.
* @default false
*/
immutable?: boolean;
}): Value<AsRequired<number, Required>, AsRequired<number, Required>, unknown>;
/** Like {@link Value.number} but options are resolved lazily at runtime via a builder function. */
static dynamicNumber<Required extends boolean, OuterType = unknown>(getA: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
default: number | null;
required: Required;
min?: number | null;
max?: number | null;
step?: number | null;
integer: boolean;
units?: string | null;
placeholder?: string | null;
disabled?: false | string;
}, OuterType>): Value<AsRequired<number, Required>, number | null, OuterType>;
/**
* @description Displays a browser-native color selector.
* @example
* ```
colorExample: Value.color({
// required
name: 'Color Example',
required: false,
default: null,
// optional
description: null,
warning: null,
immutable: false,
}),
* ```
*/
static color<Required extends boolean>(a: {
name: string;
description?: string | null;
/** Presents a warning prompt before permitting the value to change. */
warning?: string | null;
/**
* @description optionally provide a default value.
* @type { default: string | null }
* @example default: null
* @example default: 'ffffff'
*/
default: string | null;
required: Required;
/**
* @description Once set, the value can never be changed.
* @default false
*/
immutable?: boolean;
}): Value<AsRequired<string, Required>, AsRequired<string, Required>, unknown>;
/** Like {@link Value.color} but options are resolved lazily at runtime via a builder function. */
static dynamicColor<Required extends boolean, OuterType = unknown>(getA: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
default: string | null;
required: Required;
disabled?: false | string;
}, OuterType>): Value<AsRequired<string, Required>, string | null, OuterType>;
/**
* @description Displays a browser-native date/time selector.
* @example
* ```
datetimeExample: Value.datetime({
// required
name: 'Datetime Example',
required: false,
default: null,
// optional
description: null,
warning: null,
immutable: false,
inputmode: 'datetime-local',
min: null,
max: null,
}),
* ```
*/
static datetime<Required extends boolean>(a: {
name: string;
description?: string | null;
/** Presents a warning prompt before permitting the value to change. */
warning?: string | null;
/**
* @description optionally provide a default value.
* @type { default: string | null }
* @example default: null
* @example default: '1985-12-16 18:00:00.000'
*/
default: string | null;
required: Required;
/**
* @description Informs the browser how to behave and which date/time component to display.
* @default "datetime-local"
*/
inputmode?: ValueSpecDatetime['inputmode'];
min?: string | null;
max?: string | null;
/**
* @description Once set, the value can never be changed.
* @default false
*/
immutable?: boolean;
}): Value<AsRequired<string, Required>, AsRequired<string, Required>, unknown>;
/** Like {@link Value.datetime} but options are resolved lazily at runtime via a builder function. */
static dynamicDatetime<Required extends boolean, OuterType = unknown>(getA: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
default: string | null;
required: Required;
inputmode?: ValueSpecDatetime['inputmode'];
min?: string | null;
max?: string | null;
disabled?: false | string;
}, OuterType>): Value<AsRequired<string, Required>, string | null, OuterType>;
/**
* @description Displays a select modal with radio buttons, allowing for a single selection.
* @example
* ```
selectExample: Value.select({
// required
name: 'Select Example',
default: 'radio1',
values: {
radio1: 'Radio 1',
radio2: 'Radio 2',
},
// optional
description: null,
warning: null,
immutable: false,
disabled: false,
}),
* ```
*/
static select<Values extends Record<string, string>>(a: {
name: string;
description?: string | null;
/** Presents a warning prompt before permitting the value to change. */
warning?: string | null;
/**
* @description Determines if the field is required. If so, optionally provide a default value from the list of values.
* @type { (keyof Values & string) | null }
* @example default: null
* @example default: 'radio1'
*/
default: keyof Values & string;
/**
* @description A mapping of unique radio options to their human readable display format.
* @example
* ```
{
radio1: "Radio 1"
radio2: "Radio 2"
radio3: "Radio 3"
}
* ```
*/
values: Values;
/**
* @description Once set, the value can never be changed.
* @default false
*/
immutable?: boolean;
}): Value<keyof Values & string, keyof Values & string, unknown>;
/** Like {@link Value.select} but options are resolved lazily at runtime via a builder function. */
static dynamicSelect<Values extends Record<string, string>, OuterType = unknown>(getA: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
default: string;
values: Values;
disabled?: false | string | string[];
}, OuterType>): Value<keyof Values & string, keyof Values & string, OuterType>;
/**
* @description Displays a select modal with checkboxes, allowing for multiple selections.
* @example
* ```
multiselectExample: Value.multiselect({
// required
name: 'Multiselect Example',
values: {
option1: 'Option 1',
option2: 'Option 2',
},
default: [],
// optional
description: null,
warning: null,
immutable: false,
disabled: false,
minlength: null,
maxLength: null,
}),
* ```
*/
static multiselect<Values extends Record<string, string>>(a: {
name: string;
description?: string | null;
/** Presents a warning prompt before permitting the value to change. */
warning?: string | null;
/**
* @description A simple list of which options should be checked by default.
*/
default: (keyof Values & string)[];
/**
* @description A mapping of checkbox options to their human readable display format.
* @example
* ```
{
option1: "Option 1"
option2: "Option 2"
option3: "Option 3"
}
* ```
*/
values: Values;
minLength?: number | null;
maxLength?: number | null;
/**
* @description Once set, the value can never be changed.
* @default false
*/
immutable?: boolean;
}): Value<(keyof Values & string)[], (keyof Values & string)[], unknown>;
/** Like {@link Value.multiselect} but options are resolved lazily at runtime via a builder function. */
static dynamicMultiselect<Values extends Record<string, string>, OuterType = unknown>(getA: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
default: string[];
values: Values;
minLength?: number | null;
maxLength?: number | null;
disabled?: false | string | string[];
}, OuterType>): Value<(keyof Values & string)[], (keyof Values & string)[], OuterType>;
/**
* @description Display a collapsable grouping of additional fields, a "sub form". The second value is the inputSpec spec for the sub form.
* @example
* ```
objectExample: Value.object(
{
// required
name: 'Object Example',
// optional
description: null,
warning: null,
},
InputSpec.of({}),
),
* ```
*/
static object<Type extends StaticValidatedAs, StaticValidatedAs extends Record<string, any>>(a: {
name: string;
description?: string | null;
}, spec: InputSpec<Type, StaticValidatedAs>): Value<Type, StaticValidatedAs, unknown>;
/**
* Displays a file upload input field.
*
* @param a.extensions - Allowed file extensions (e.g. `[".pem", ".crt"]`)
* @param a.required - Whether a file must be selected
*/
static file<Required extends boolean>(a: {
name: string;
description?: string | null;
warning?: string | null;
extensions: string[];
required: Required;
}): Value<AsRequired<{
path: string;
commitment: {
hash: string;
size: number;
};
}, Required>, AsRequired<{
path: string;
commitment: {
hash: string;
size: number;
};
}, Required>, unknown>;
/** Like {@link Value.file} but options are resolved lazily at runtime via a builder function. */
static dynamicFile<Required extends boolean, OuterType = unknown>(a: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
extensions: string[];
required: Required;
}, OuterType>): Value<AsRequired<{
path: string;
commitment: {
hash: string;
size: number;
};
}, Required>, {
path: string;
commitment: {
hash: string;
size: number;
};
} | null, OuterType>;
/**
* @description Displays a dropdown, allowing for a single selection. Depending on the selection, a different object ("sub form") is presented.
* @example
* ```
unionExample: Value.union(
{
// required
name: 'Union Example',
default: 'option1',
// optional
description: null,
warning: null,
disabled: false,
immutable: false,
},
Variants.of({
option1: {
name: 'Option 1',
spec: InputSpec.of({}),
},
option2: {
name: 'Option 2',
spec: InputSpec.of({}),
},
}),
),
* ```
*/
static union<VariantValues extends {
[K in string]: {
name: string;
spec: InputSpec<any>;
};
}>(a: {
name: string;
description?: string | null;
/** Presents a warning prompt before permitting the value to change. */
warning?: string | null;
variants: Variants<VariantValues>;
/**
* @description Provide a default value from the list of variants.
* @type { string }
* @example default: 'variant1'
*/
default: keyof VariantValues & string;
/**
* @description Once set, the value can never be changed.
* @default false
*/
immutable?: boolean;
}): Value<UnionRes<VariantValues, keyof VariantValues & string>, UnionResStaticValidatedAs<VariantValues, keyof VariantValues & string>, unknown>;
/** Like {@link Value.union} but options (including which variants are available) are resolved lazily at runtime. */
static dynamicUnion<VariantValues extends {
[K in string]: {
name: string;
spec: InputSpec<any>;
};
}, OuterType = unknown>(getA: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
variants: Variants<VariantValues>;
default: keyof VariantValues & string;
disabled: string[] | false | string;
}, OuterType>): Value<UnionRes<VariantValues>, UnionRes<VariantValues>, OuterType>;
/** Like {@link Value.union} but options are resolved lazily, with an explicit static validator type. */
static dynamicUnion<StaticVariantValues extends {
[K in string]: {
name: string;
spec: InputSpec<any, any>;
};
}, VariantValues extends StaticVariantValues, OuterType = unknown>(getA: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
variants: Variants<VariantValues>;
default: keyof VariantValues & string;
disabled: string[] | false | string;
}, OuterType>, validator: z.ZodType<UnionResStaticValidatedAs<StaticVariantValues>>): Value<UnionRes<VariantValues>, UnionResStaticValidatedAs<StaticVariantValues>, OuterType>;
/**
* @description Presents an interface to add/remove/edit items in a list.
* @example
* In this example, we create a list of text inputs.
*
* ```
listExampleText: Value.list(
List.text(
{
// required
name: 'Text List',
// optional
description: null,
warning: null,
default: [],
minLength: null,
maxLength: null,
},
{
// required
patterns: [],
// optional
placeholder: null,
generate: null,
inputmode: 'url',
masked: false,
minLength: null,
maxLength: null,
},
),
),
* ```
* @example
* In this example, we create a list of objects.
*
* ```
listExampleObject: Value.list(
List.obj(
{
// required
name: 'Object List',
// optional
description: null,
warning: null,
default: [],
minLength: null,
maxLength: null,
},
{
// required
spec: InputSpec.of({}),
// optional
displayAs: null,
uniqueBy: null,
},
),
),
* ```
*/
static list<Type>(a: List<Type>): Value<Type, Type, unknown>;
/**
* @description Provides a way to define a hidden field with a static value. Useful for tracking
* @example
* ```
hiddenExample: Value.hidden(),
* ```
*/
static hidden<T>(): Value<T>;
static hidden<T>(parser: z.ZodType<T>): Value<T>;
/**
* @description Provides a way to define a hidden field with a static value. Useful for tracking
* @example
* ```
hiddenExample: Value.hidden(),
* ```
*/
static dynamicHidden<T, OuterType = unknown>(getParser: LazyBuild<z.ZodType<T>, OuterType>): Value<T, T, OuterType>;
/**
* Returns a new Value that produces the same field spec but with `disabled` set to the given message.
* The field remains in the form but cannot be edited by the user.
*
* @param message - The reason the field is disabled, displayed to the user
*/
withDisabled(message: string): Value<Type, StaticValidatedAs, OuterType>;
/**
* Transforms the validated output value using a mapping function.
* The form field itself remains unchanged, but the value is transformed after validation.
*
* @param fn - A function to transform the validated value
*/
map<U>(fn: (value: StaticValidatedAs) => U): Value<U, U, OuterType>;
}
@@ -0,0 +1,764 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Value = exports.fileInfoParser = void 0;
const util_1 = require("../../../util");
const zod_1 = require("zod");
/** Build a union-of-literals validator from object keys, falling back to z.string() when empty */
function literalKeysValidator(values) {
const keys = Object.keys(values);
if (keys.length === 0)
return zod_1.z.string();
return zod_1.z.union(keys.map((x) => zod_1.z.literal(x)));
}
/** Zod schema for a file upload result — validates `{ path, commitment: { hash, size } }`. */
exports.fileInfoParser = zod_1.z.object({
path: zod_1.z.string(),
commitment: zod_1.z.object({ hash: zod_1.z.string(), size: zod_1.z.number() }),
});
const testForAsRequiredParser = (0, util_1.once)(() => (v) => zod_1.z.object({ required: zod_1.z.literal(true) }).safeParse(v).success);
function asRequiredParser(parser, input) {
if (testForAsRequiredParser()(input))
return parser;
return parser.nullable();
}
/**
* Core builder class for defining a single form field in a service configuration spec.
*
* Each static factory method (e.g. `Value.text()`, `Value.toggle()`, `Value.select()`) creates
* a typed `Value` instance representing a specific field type. Dynamic variants (e.g. `Value.dynamicText()`)
* allow the field options to be computed lazily at runtime.
*
* Use with {@link InputSpec} to compose complete form specifications.
*
* @typeParam Type - The runtime type this field produces when filled in
* @typeParam StaticValidatedAs - The compile-time validated type (usually same as Type)
* @typeParam OuterType - The parent form's type context (used by dynamic variants)
*/
class Value {
constructor(build, validator) {
this.build = build;
this.validator = validator;
this._TYPE = null;
this._PARTIAL = null;
}
/**
* @description Displays a boolean toggle to enable/disable
* @example
* ```
toggleExample: Value.toggle({
// required
name: 'Toggle Example',
default: true,
// optional
description: null,
warning: null,
immutable: false,
}),
* ```
*/
static toggle(a) {
const validator = zod_1.z.boolean();
return new Value(async () => ({
spec: {
description: null,
warning: null,
type: 'toggle',
disabled: false,
immutable: a.immutable ?? false,
...a,
},
validator,
}), validator);
}
/** Like {@link Value.toggle} but options are resolved lazily at runtime via a builder function. */
static dynamicToggle(a) {
const validator = zod_1.z.boolean();
return new Value(async (options) => ({
spec: {
description: null,
warning: null,
type: 'toggle',
disabled: false,
immutable: false,
...(await a(options)),
},
validator,
}), validator);
}
/**
* @description Displays a text input field
* @example
* ```
textExample: Value.text({
// required
name: 'Text Example',
required: false,
default: null,
// optional
description: null,
placeholder: null,
warning: null,
generate: null,
inputmode: 'text',
masked: false,
minLength: null,
maxLength: null,
patterns: [],
immutable: false,
}),
* ```
*/
static text(a) {
const validator = asRequiredParser(zod_1.z.string(), a);
return new Value(async () => ({
spec: {
type: 'text',
description: null,
warning: null,
masked: false,
placeholder: null,
minLength: null,
maxLength: null,
patterns: [],
inputmode: 'text',
disabled: false,
immutable: a.immutable ?? false,
generate: a.generate ?? null,
...a,
},
validator,
}), validator);
}
/** Like {@link Value.text} but options are resolved lazily at runtime via a builder function. */
static dynamicText(getA) {
return new Value(async (options) => {
const a = await getA(options);
return {
spec: {
type: 'text',
description: null,
warning: null,
masked: false,
placeholder: null,
minLength: null,
maxLength: null,
patterns: [],
inputmode: 'text',
disabled: false,
immutable: false,
generate: a.generate ?? null,
...a,
},
validator: asRequiredParser(zod_1.z.string(), a),
};
}, zod_1.z.string().nullable());
}
/**
* @description Displays a large textarea field for long form entry.
* @example
* ```
textareaExample: Value.textarea({
// required
name: 'Textarea Example',
required: false,
default: null,
// optional
description: null,
placeholder: null,
warning: null,
minLength: null,
maxLength: null,
minRows: 3
maxRows: 6
immutable: false,
}),
* ```
*/
static textarea(a) {
const validator = asRequiredParser(zod_1.z.string(), a);
return new Value(async () => {
const built = {
description: null,
warning: null,
minLength: null,
maxLength: null,
patterns: [],
minRows: 3,
maxRows: 6,
placeholder: null,
type: 'textarea',
disabled: false,
immutable: a.immutable ?? false,
...a,
};
return { spec: built, validator };
}, validator);
}
/** Like {@link Value.textarea} but options are resolved lazily at runtime via a builder function. */
static dynamicTextarea(getA) {
return new Value(async (options) => {
const a = await getA(options);
return {
spec: {
description: null,
warning: null,
minLength: null,
maxLength: null,
patterns: [],
minRows: 3,
maxRows: 6,
placeholder: null,
type: 'textarea',
disabled: false,
immutable: false,
...a,
},
validator: asRequiredParser(zod_1.z.string(), a),
};
}, zod_1.z.string().nullable());
}
/**
* @description Displays a number input field
* @example
* ```
numberExample: Value.number({
// required
name: 'Number Example',
required: false,
default: null,
integer: true,
// optional
description: null,
placeholder: null,
warning: null,
min: null,
max: null,
immutable: false,
step: null,
units: null,
}),
* ```
*/
static number(a) {
const validator = asRequiredParser(zod_1.z.number(), a);
return new Value(() => ({
spec: {
type: 'number',
description: null,
warning: null,
min: null,
max: null,
step: null,
units: null,
placeholder: null,
disabled: false,
immutable: a.immutable ?? false,
...a,
},
validator,
}), validator);
}
/** Like {@link Value.number} but options are resolved lazily at runtime via a builder function. */
static dynamicNumber(getA) {
return new Value(async (options) => {
const a = await getA(options);
return {
spec: {
type: 'number',
description: null,
warning: null,
min: null,
max: null,
step: null,
units: null,
placeholder: null,
disabled: false,
immutable: false,
...a,
},
validator: asRequiredParser(zod_1.z.number(), a),
};
}, zod_1.z.number().nullable());
}
/**
* @description Displays a browser-native color selector.
* @example
* ```
colorExample: Value.color({
// required
name: 'Color Example',
required: false,
default: null,
// optional
description: null,
warning: null,
immutable: false,
}),
* ```
*/
static color(a) {
const validator = asRequiredParser(zod_1.z.string(), a);
return new Value(() => ({
spec: {
type: 'color',
description: null,
warning: null,
disabled: false,
immutable: a.immutable ?? false,
...a,
},
validator,
}), validator);
}
/** Like {@link Value.color} but options are resolved lazily at runtime via a builder function. */
static dynamicColor(getA) {
return new Value(async (options) => {
const a = await getA(options);
return {
spec: {
type: 'color',
description: null,
warning: null,
disabled: false,
immutable: false,
...a,
},
validator: asRequiredParser(zod_1.z.string(), a),
};
}, zod_1.z.string().nullable());
}
/**
* @description Displays a browser-native date/time selector.
* @example
* ```
datetimeExample: Value.datetime({
// required
name: 'Datetime Example',
required: false,
default: null,
// optional
description: null,
warning: null,
immutable: false,
inputmode: 'datetime-local',
min: null,
max: null,
}),
* ```
*/
static datetime(a) {
const validator = asRequiredParser(zod_1.z.string(), a);
return new Value(() => ({
spec: {
type: 'datetime',
description: null,
warning: null,
inputmode: 'datetime-local',
min: null,
max: null,
step: null,
disabled: false,
immutable: a.immutable ?? false,
...a,
},
validator,
}), validator);
}
/** Like {@link Value.datetime} but options are resolved lazily at runtime via a builder function. */
static dynamicDatetime(getA) {
return new Value(async (options) => {
const a = await getA(options);
return {
spec: {
type: 'datetime',
description: null,
warning: null,
inputmode: 'datetime-local',
min: null,
max: null,
disabled: false,
immutable: false,
...a,
},
validator: asRequiredParser(zod_1.z.string(), a),
};
}, zod_1.z.string().nullable());
}
/**
* @description Displays a select modal with radio buttons, allowing for a single selection.
* @example
* ```
selectExample: Value.select({
// required
name: 'Select Example',
default: 'radio1',
values: {
radio1: 'Radio 1',
radio2: 'Radio 2',
},
// optional
description: null,
warning: null,
immutable: false,
disabled: false,
}),
* ```
*/
static select(a) {
const validator = literalKeysValidator(a.values);
return new Value(() => ({
spec: {
description: null,
warning: null,
type: 'select',
disabled: false,
immutable: a.immutable ?? false,
...a,
},
validator,
}), validator);
}
/** Like {@link Value.select} but options are resolved lazily at runtime via a builder function. */
static dynamicSelect(getA) {
return new Value(async (options) => {
const a = await getA(options);
return {
spec: {
description: null,
warning: null,
type: 'select',
disabled: false,
immutable: false,
...a,
},
validator: literalKeysValidator(a.values),
};
}, zod_1.z.string());
}
/**
* @description Displays a select modal with checkboxes, allowing for multiple selections.
* @example
* ```
multiselectExample: Value.multiselect({
// required
name: 'Multiselect Example',
values: {
option1: 'Option 1',
option2: 'Option 2',
},
default: [],
// optional
description: null,
warning: null,
immutable: false,
disabled: false,
minlength: null,
maxLength: null,
}),
* ```
*/
static multiselect(a) {
const validator = zod_1.z.array(literalKeysValidator(a.values));
return new Value(() => ({
spec: {
type: 'multiselect',
minLength: null,
maxLength: null,
warning: null,
description: null,
disabled: false,
immutable: a.immutable ?? false,
...a,
},
validator,
}), validator);
}
/** Like {@link Value.multiselect} but options are resolved lazily at runtime via a builder function. */
static dynamicMultiselect(getA) {
return new Value(async (options) => {
const a = await getA(options);
return {
spec: {
type: 'multiselect',
minLength: null,
maxLength: null,
warning: null,
description: null,
disabled: false,
immutable: false,
...a,
},
validator: zod_1.z.array(literalKeysValidator(a.values)),
};
}, zod_1.z.array(zod_1.z.string()));
}
/**
* @description Display a collapsable grouping of additional fields, a "sub form". The second value is the inputSpec spec for the sub form.
* @example
* ```
objectExample: Value.object(
{
// required
name: 'Object Example',
// optional
description: null,
warning: null,
},
InputSpec.of({}),
),
* ```
*/
static object(a, spec) {
const value = new Value(async (options) => {
const built = await spec.build(options);
return {
spec: {
type: 'object',
description: null,
warning: null,
...a,
spec: built.spec,
},
validator: built.validator,
};
}, spec.validator);
value._objectSpec = { inputSpec: spec, params: a };
return value;
}
/**
* Displays a file upload input field.
*
* @param a.extensions - Allowed file extensions (e.g. `[".pem", ".crt"]`)
* @param a.required - Whether a file must be selected
*/
static file(a) {
const buildValue = {
type: 'file',
description: null,
warning: null,
...a,
};
return new Value(() => ({
spec: {
...buildValue,
},
validator: asRequiredParser(exports.fileInfoParser, a),
}), asRequiredParser(exports.fileInfoParser, a));
}
/** Like {@link Value.file} but options are resolved lazily at runtime via a builder function. */
static dynamicFile(a) {
return new Value(async (options) => {
const spec = {
type: 'file',
description: null,
warning: null,
...(await a(options)),
};
return {
spec,
validator: asRequiredParser(exports.fileInfoParser, spec),
};
}, exports.fileInfoParser.nullable());
}
/**
* @description Displays a dropdown, allowing for a single selection. Depending on the selection, a different object ("sub form") is presented.
* @example
* ```
unionExample: Value.union(
{
// required
name: 'Union Example',
default: 'option1',
// optional
description: null,
warning: null,
disabled: false,
immutable: false,
},
Variants.of({
option1: {
name: 'Option 1',
spec: InputSpec.of({}),
},
option2: {
name: 'Option 2',
spec: InputSpec.of({}),
},
}),
),
* ```
*/
static union(a) {
return new Value(async (options) => {
const built = await a.variants.build(options);
return {
spec: {
type: 'union',
description: null,
warning: null,
disabled: false,
...a,
variants: built.spec,
immutable: a.immutable ?? false,
},
validator: built.validator,
};
}, a.variants.validator);
}
static dynamicUnion(getA, validator = zod_1.z.any()) {
return new Value(async (options) => {
const newValues = await getA(options);
const built = await newValues.variants.build(options);
return {
spec: {
type: 'union',
description: null,
warning: null,
...newValues,
variants: built.spec,
immutable: false,
},
validator: built.validator,
};
}, validator);
}
/**
* @description Presents an interface to add/remove/edit items in a list.
* @example
* In this example, we create a list of text inputs.
*
* ```
listExampleText: Value.list(
List.text(
{
// required
name: 'Text List',
// optional
description: null,
warning: null,
default: [],
minLength: null,
maxLength: null,
},
{
// required
patterns: [],
// optional
placeholder: null,
generate: null,
inputmode: 'url',
masked: false,
minLength: null,
maxLength: null,
},
),
),
* ```
* @example
* In this example, we create a list of objects.
*
* ```
listExampleObject: Value.list(
List.obj(
{
// required
name: 'Object List',
// optional
description: null,
warning: null,
default: [],
minLength: null,
maxLength: null,
},
{
// required
spec: InputSpec.of({}),
// optional
displayAs: null,
uniqueBy: null,
},
),
),
* ```
*/
static list(a) {
return new Value((options) => a.build(options), a.validator);
}
static hidden(parser = zod_1.z.any()) {
return new Value(async () => {
return {
spec: {
type: 'hidden',
},
validator: parser,
};
}, parser);
}
/**
* @description Provides a way to define a hidden field with a static value. Useful for tracking
* @example
* ```
hiddenExample: Value.hidden(),
* ```
*/
static dynamicHidden(getParser) {
return new Value(async (options) => {
const validator = await getParser(options);
return {
spec: {
type: 'hidden',
},
validator,
};
}, zod_1.z.any());
}
/**
* Returns a new Value that produces the same field spec but with `disabled` set to the given message.
* The field remains in the form but cannot be edited by the user.
*
* @param message - The reason the field is disabled, displayed to the user
*/
withDisabled(message) {
const original = this;
const v = new Value(async (options) => {
const built = await original.build(options);
return {
spec: { ...built.spec, disabled: message },
validator: built.validator,
};
}, this.validator);
v._objectSpec = this._objectSpec;
return v;
}
/**
* Transforms the validated output value using a mapping function.
* The form field itself remains unchanged, but the value is transformed after validation.
*
* @param fn - A function to transform the validated value
*/
map(fn) {
return new Value(async (options) => {
const built = await this.build(options);
return {
spec: built.spec,
validator: built.validator.transform(fn),
};
}, this.validator.transform(fn));
}
}
exports.Value = Value;
//# sourceMappingURL=value.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,111 @@
import { DeepPartial } from '../../../types';
import { ValueSpecUnion } from '../inputSpecTypes';
import { LazyBuild, InputSpec, ExtractInputSpecType, ExtractInputSpecStaticValidatedAs } from './inputSpec';
import { z } from 'zod';
/**
* The runtime result type of a discriminated union form field.
* Contains `selection` (the chosen variant key), `value` (the variant's form data),
* and optionally `other` (partial data from previously selected variants).
*/
export type UnionRes<VariantValues extends {
[K in string]: {
name: string;
spec: InputSpec<any>;
};
}, K extends keyof VariantValues & string = keyof VariantValues & string> = {
[key in keyof VariantValues]: {
selection: key;
value: ExtractInputSpecType<VariantValues[key]['spec']>;
other?: {
[key2 in Exclude<keyof VariantValues & string, key>]?: DeepPartial<ExtractInputSpecType<VariantValues[key2]['spec']>>;
};
};
}[K];
/** Like {@link UnionRes} but using the static (Zod-inferred) validated types. */
export type UnionResStaticValidatedAs<VariantValues extends {
[K in string]: {
name: string;
spec: InputSpec<any>;
};
}, K extends keyof VariantValues & string = keyof VariantValues & string> = {
[key in keyof VariantValues]: {
selection: key;
value: ExtractInputSpecStaticValidatedAs<VariantValues[key]['spec']>;
other?: {
[key2 in Exclude<keyof VariantValues & string, key>]?: DeepPartial<ExtractInputSpecStaticValidatedAs<VariantValues[key2]['spec']>>;
};
};
}[K];
/**
* Used in the the Value.select { @link './value.ts' }
* to indicate the type of select variants that are available. The key for the record passed in will be the
* key to the tag.id in the Value.select
```ts
export const disabled = InputSpec.of({});
export const size = Value.number({
name: "Max Chain Size",
default: 550,
description: "Limit of blockchain size on disk.",
warning: "Increasing this value will require re-syncing your node.",
required: true,
range: "[550,1000000)",
integral: true,
units: "MiB",
placeholder: null,
});
export const automatic = InputSpec.of({ size: size });
export const size1 = Value.number({
name: "Failsafe Chain Size",
default: 65536,
description: "Prune blockchain if size expands beyond this.",
warning: null,
required: true,
range: "[550,1000000)",
integral: true,
units: "MiB",
placeholder: null,
});
export const manual = InputSpec.of({ size: size1 });
export const pruningSettingsVariants = Variants.of({
disabled: { name: "Disabled", spec: disabled },
automatic: { name: "Automatic", spec: automatic },
manual: { name: "Manual", spec: manual },
});
export const pruning = Value.union(
{
name: "Pruning Settings",
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',
warning: null,
default: "disabled",
},
pruningSettingsVariants
);
```
*/
export declare class Variants<VariantValues extends {
[K in string]: {
name: string;
spec: InputSpec<any, any>;
};
}, OuterType = unknown> {
build: LazyBuild<{
spec: ValueSpecUnion['variants'];
validator: z.ZodType<UnionRes<VariantValues>>;
}, OuterType>;
readonly validator: z.ZodType<UnionResStaticValidatedAs<VariantValues>>;
private constructor();
readonly _TYPE: UnionRes<VariantValues>;
/**
* Creates a `Variants` instance from a record mapping variant keys to their display name and form spec.
*
* @param a - A record of `{ name: string, spec: InputSpec }` entries, one per variant
*/
static of<VariantValues extends {
[K in string]: {
name: string;
spec: InputSpec<any>;
};
}>(a: VariantValues): Variants<VariantValues, unknown>;
}
@@ -0,0 +1,110 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Variants = void 0;
const zod_1 = require("zod");
/**
* Used in the the Value.select { @link './value.ts' }
* to indicate the type of select variants that are available. The key for the record passed in will be the
* key to the tag.id in the Value.select
```ts
export const disabled = InputSpec.of({});
export const size = Value.number({
name: "Max Chain Size",
default: 550,
description: "Limit of blockchain size on disk.",
warning: "Increasing this value will require re-syncing your node.",
required: true,
range: "[550,1000000)",
integral: true,
units: "MiB",
placeholder: null,
});
export const automatic = InputSpec.of({ size: size });
export const size1 = Value.number({
name: "Failsafe Chain Size",
default: 65536,
description: "Prune blockchain if size expands beyond this.",
warning: null,
required: true,
range: "[550,1000000)",
integral: true,
units: "MiB",
placeholder: null,
});
export const manual = InputSpec.of({ size: size1 });
export const pruningSettingsVariants = Variants.of({
disabled: { name: "Disabled", spec: disabled },
automatic: { name: "Automatic", spec: automatic },
manual: { name: "Manual", spec: manual },
});
export const pruning = Value.union(
{
name: "Pruning Settings",
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',
warning: null,
default: "disabled",
},
pruningSettingsVariants
);
```
*/
class Variants {
constructor(build, validator) {
this.build = build;
this.validator = validator;
this._TYPE = null;
}
/**
* Creates a `Variants` instance from a record mapping variant keys to their display name and form spec.
*
* @param a - A record of `{ name: string, spec: InputSpec }` entries, one per variant
*/
static of(a) {
const staticValidators = {};
for (const key in a) {
const value = a[key];
staticValidators[key] = value.spec.validator;
}
const other = zod_1.z
.object(Object.fromEntries(Object.entries(staticValidators).map(([k, v]) => [
k,
zod_1.z.any().optional(),
])))
.optional();
return new Variants(async (options) => {
const validators = {};
const variants = {};
for (const key in a) {
const value = a[key];
const built = await value.spec.build(options);
variants[key] = {
name: value.name,
spec: built.spec,
};
validators[key] = built.validator;
}
const other = zod_1.z
.object(Object.fromEntries(Object.entries(validators).map(([k, v]) => [
k,
zod_1.z.any().optional(),
])))
.optional();
return {
spec: variants,
validator: zod_1.z.union(Object.entries(validators).map(([k, v]) => zod_1.z.object({
selection: zod_1.z.literal(k),
value: v,
other,
}))),
};
}, zod_1.z.union(Object.entries(staticValidators).map(([k, v]) => zod_1.z.object({
selection: zod_1.z.literal(k),
value: v,
other,
}))));
}
}
exports.Variants = Variants;
//# sourceMappingURL=variants.js.map
@@ -0,0 +1 @@
{"version":3,"file":"variants.js","sourceRoot":"","sources":["../../../../../../base/lib/actions/input/builder/variants.ts"],"names":[],"mappings":";;;AAQA,6BAAuB;AAgDvB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,MAAa,QAAQ;IASnB,YACS,KAMN,EACe,SAEf;QATM,UAAK,GAAL,KAAK,CAMX;QACe,cAAS,GAAT,SAAS,CAExB;QAEM,UAAK,GAA4B,IAAW,CAAA;IADlD,CAAC;IAEJ;;;;OAIG;IACH,MAAM,CAAC,EAAE,CAOP,CAAgB;QAChB,MAAM,gBAAgB,GAAG,EAIxB,CAAA;QACD,KAAK,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,CAAA;YACpB,gBAAgB,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,SAAS,CAAA;QAC9C,CAAC;QACD,MAAM,KAAK,GAAG,OAAC;aACZ,MAAM,CACL,MAAM,CAAC,WAAW,CAChB,MAAM,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC;YAC/C,CAAC;YACD,OAAC,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;SACnB,CAAC,CACH,CACF;aACA,QAAQ,EAAE,CAAA;QACb,OAAO,IAAI,QAAQ,CACjB,KAAK,EAAE,OAAO,EAAE,EAAE;YAChB,MAAM,UAAU,GAAG,EAIlB,CAAA;YACD,MAAM,QAAQ,GAAG,EAKhB,CAAA;YACD,KAAK,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;gBACpB,MAAM,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,CAAA;gBACpB,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAc,CAAC,CAAA;gBACpD,QAAQ,CAAC,GAAG,CAAC,GAAG;oBACd,IAAI,EAAE,KAAK,CAAC,IAAI;oBAChB,IAAI,EAAE,KAAK,CAAC,IAAI;iBACjB,CAAA;gBACD,UAAU,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,SAAS,CAAA;YACnC,CAAC;YACD,MAAM,KAAK,GAAG,OAAC;iBACZ,MAAM,CACL,MAAM,CAAC,WAAW,CAChB,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC;gBACzC,CAAC;gBACD,OAAC,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;aACnB,CAAC,CACH,CACF;iBACA,QAAQ,EAAE,CAAA;YACb,OAAO;gBACL,IAAI,EAAE,QAAQ;gBACd,SAAS,EAAE,OAAC,CAAC,KAAK,CAChB,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CACxC,OAAC,CAAC,MAAM,CAAC;oBACP,SAAS,EAAE,OAAC,CAAC,OAAO,CAAC,CAAC,CAAC;oBACvB,KAAK,EAAE,CAAC;oBACR,KAAK;iBACN,CAAC,CAC4D,CAC1D;aACT,CAAA;QACH,CAAC,EACD,OAAC,CAAC,KAAK,CACL,MAAM,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAC9C,OAAC,CAAC,MAAM,CAAC;YACP,SAAS,EAAE,OAAC,CAAC,OAAO,CAAC,CAAC,CAAC;YACvB,KAAK,EAAE,CAAC;YACR,KAAK;SACN,CAAC,CAC4D,CAC1D,CACT,CAAA;IACH,CAAC;CACF;AA9GD,4BA8GC"}
+3
View File
@@ -0,0 +1,3 @@
export * as constants from './inputSpecConstants';
export * as types from './inputSpecTypes';
export * as builder from './builder';
+40
View File
@@ -0,0 +1,40 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.builder = exports.types = exports.constants = void 0;
exports.constants = __importStar(require("./inputSpecConstants"));
exports.types = __importStar(require("./inputSpecTypes"));
exports.builder = __importStar(require("./builder"));
//# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../base/lib/actions/input/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,kEAAiD;AACjD,0DAAyC;AACzC,qDAAoC"}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,247 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.smtpShape = exports.smtpInputSpec = exports.systemSmtpSpec = exports.smtpProviderVariants = exports.customSmtp = void 0;
exports.smtpPrefill = smtpPrefill;
const util_1 = require("../../util");
const inputSpec_1 = require("./builder/inputSpec");
const value_1 = require("./builder/value");
const variants_1 = require("./builder/variants");
const zod_1 = require("zod");
const securityVariants = variants_1.Variants.of({
tls: {
name: 'TLS',
spec: inputSpec_1.InputSpec.of({
port: value_1.Value.dynamicText(async () => ({
name: 'Port',
required: true,
default: '465',
disabled: 'Fixed for TLS',
})),
}),
},
starttls: {
name: 'STARTTLS',
spec: inputSpec_1.InputSpec.of({
port: value_1.Value.select({
name: 'Port',
default: '587',
values: { '25': '25', '587': '587', '2525': '2525' },
}),
}),
},
});
/**
* Creates an SMTP field spec with provider-specific defaults pre-filled.
*/
function smtpFields(defaults = {}) {
const hostSpec = value_1.Value.text({
name: 'Host',
required: true,
default: defaults.host ?? null,
placeholder: 'smtp.example.com',
});
return inputSpec_1.InputSpec.of({
host: defaults.hostDisabled
? hostSpec.withDisabled('Fixed for this provider')
: hostSpec,
security: value_1.Value.union({
name: 'Connection Security',
default: defaults.security ?? 'tls',
variants: securityVariants,
}),
from: value_1.Value.text({
name: 'From Address',
required: true,
default: null,
placeholder: 'Example Name <test@example.com>',
patterns: [util_1.Patterns.emailWithName],
}),
username: value_1.Value.text({
name: 'Username',
required: true,
default: null,
}),
password: value_1.Value.text({
name: 'Password',
required: false,
default: null,
masked: true,
}),
});
}
/**
* Base SMTP settings with no provider-specific defaults.
*/
exports.customSmtp = smtpFields();
/**
* Provider presets for SMTP configuration.
* Each variant has SMTP fields pre-filled with the provider's recommended settings.
*/
exports.smtpProviderVariants = variants_1.Variants.of({
gmail: {
name: 'Gmail',
spec: smtpFields({
host: 'smtp.gmail.com',
security: 'tls',
hostDisabled: true,
}),
},
ses: {
name: 'Amazon SES',
spec: smtpFields({
host: 'email-smtp.us-east-1.amazonaws.com',
security: 'tls',
}),
},
sendgrid: {
name: 'SendGrid',
spec: smtpFields({
host: 'smtp.sendgrid.net',
security: 'tls',
hostDisabled: true,
}),
},
mailgun: {
name: 'Mailgun',
spec: smtpFields({
host: 'smtp.mailgun.org',
security: 'tls',
hostDisabled: true,
}),
},
protonmail: {
name: 'Proton Mail',
spec: smtpFields({
host: 'smtp.protonmail.ch',
security: 'tls',
hostDisabled: true,
}),
},
other: {
name: 'Other',
spec: exports.customSmtp,
},
});
/**
* System SMTP settings with provider presets.
* Wraps smtpProviderVariants in a union for use by the system email settings page.
*/
exports.systemSmtpSpec = inputSpec_1.InputSpec.of({
provider: value_1.Value.union({
name: 'Provider',
default: 'gmail',
variants: exports.smtpProviderVariants,
}),
});
const smtpVariants = variants_1.Variants.of({
disabled: { name: 'Disabled', spec: inputSpec_1.InputSpec.of({}) },
system: {
name: 'System Credentials',
spec: inputSpec_1.InputSpec.of({
customFrom: value_1.Value.text({
name: 'Custom From Address',
description: 'A custom from address for this service. If not provided, the system from address will be used.',
required: false,
default: null,
placeholder: 'Name <test@example.com>',
patterns: [util_1.Patterns.emailWithName],
}),
}),
},
custom: {
name: 'Custom Credentials',
spec: inputSpec_1.InputSpec.of({
provider: value_1.Value.union({
name: 'Provider',
default: null,
variants: exports.smtpProviderVariants,
}),
}),
},
});
/**
* For service inputSpec. Gives users 3 options for SMTP: (1) disabled, (2) use system SMTP settings, (3) use custom SMTP settings with provider presets
*/
exports.smtpInputSpec = value_1.Value.dynamicUnion(async ({ effects }) => {
const smtp = await new util_1.GetSystemSmtp(effects).once();
const disabled = smtp ? [] : ['system'];
return {
name: 'SMTP',
description: 'Optionally provide an SMTP server for sending emails',
default: 'disabled',
disabled,
variants: smtpVariants,
};
}, smtpVariants.validator);
const securityShape = zod_1.z
.object({
selection: zod_1.z.enum(['tls', 'starttls']).catch('tls'),
value: zod_1.z.object({ port: zod_1.z.string().catch('465') }).catch({ port: '465' }),
})
.catch({ selection: 'tls', value: { port: '465' } });
const providerShape = zod_1.z
.object({
selection: zod_1.z.string().catch('other'),
value: zod_1.z
.object({
host: zod_1.z.string().catch(''),
from: zod_1.z.string().catch(''),
username: zod_1.z.string().catch(''),
password: zod_1.z.string().nullable().optional().catch(null),
security: securityShape,
})
.catch({
host: '',
from: '',
username: '',
password: null,
security: securityShape.parse(undefined),
}),
})
.catch({
selection: 'other',
value: {
host: '',
from: '',
username: '',
password: null,
security: securityShape.parse(undefined),
},
});
/**
* Zod schema for persisting SMTP selection in a store file model.
* Use this instead of `smtpInputSpec.validator` to avoid cross-zod-instance issues.
*/
exports.smtpShape = zod_1.z
.discriminatedUnion('selection', [
zod_1.z.object({
selection: zod_1.z.literal('disabled'),
value: zod_1.z.object({}).catch({}),
}),
zod_1.z.object({
selection: zod_1.z.literal('system'),
value: zod_1.z
.object({ customFrom: zod_1.z.string().nullable().optional().catch(null) })
.catch({ customFrom: null }),
}),
zod_1.z.object({
selection: zod_1.z.literal('custom'),
value: zod_1.z
.object({ provider: providerShape })
.catch({ provider: providerShape.parse(undefined) }),
}),
])
.catch({ selection: 'disabled', value: {} });
/**
* Convert a stored SmtpSelection to a value suitable for prefilling smtpInputSpec.
*
* The stored type (SmtpSelection from smtpShape) uses flat unions for provider/security
* selection, while the input spec (smtpInputSpec) uses distributed discriminated unions
* (UnionRes). These are structurally incompatible in TypeScript's type system, even
* though the runtime values are identical. This function bridges the two types so that
* service code doesn't need `as any`.
*/
function smtpPrefill(smtp) {
return smtp || undefined;
}
//# sourceMappingURL=inputSpecConstants.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,285 @@
/**
* A record mapping field keys to their {@link ValueSpec} definitions.
* This is the root shape of a dynamic form specification — it defines the complete set
* of configurable fields for a service or action.
*/
export type InputSpec = Record<string, ValueSpec>;
/**
* The discriminator for all supported form field types.
*/
export type ValueType = 'text' | 'textarea' | 'number' | 'color' | 'datetime' | 'toggle' | 'select' | 'multiselect' | 'list' | 'object' | 'file' | 'union' | 'hidden';
/** Union of all concrete form field spec types. Discriminate on the `type` field. */
export type ValueSpec = ValueSpecOf<ValueType>;
/** core spec types. These types provide the metadata for performing validations */
export type ValueSpecOf<T extends ValueType> = T extends "text" ? ValueSpecText : T extends "textarea" ? ValueSpecTextarea : T extends "number" ? ValueSpecNumber : T extends "color" ? ValueSpecColor : T extends "datetime" ? ValueSpecDatetime : T extends "toggle" ? ValueSpecToggle : T extends "select" ? ValueSpecSelect : T extends "multiselect" ? ValueSpecMultiselect : T extends "list" ? ValueSpecList : T extends "object" ? ValueSpecObject : T extends "file" ? ValueSpecFile : T extends "union" ? ValueSpecUnion : T extends "hidden" ? ValueSpecHidden : never;
/** Spec for a single-line text input field. */
export type ValueSpecText = {
/** Display label for the field. */
name: string;
/** Optional help text displayed below the field. */
description: string | null;
/** Optional warning message displayed to the user. */
warning: string | null;
type: 'text';
/** Regex patterns used to validate the input value. */
patterns: Pattern[];
/** Minimum character length, or `null` for no minimum. */
minLength: number | null;
/** Maximum character length, or `null` for no maximum. */
maxLength: number | null;
/** Whether the field should obscure input (e.g. for passwords). */
masked: boolean;
/** HTML input mode hint for mobile keyboards. */
inputmode: 'text' | 'email' | 'tel' | 'url';
/** Placeholder text shown when the field is empty. */
placeholder: string | null;
/** Whether the field must have a value. */
required: boolean;
/** Default value, which may be a literal string or a {@link RandomString} generation spec. */
default: DefaultString | null;
/** `false` if editable, or a string message explaining why the field is disabled. */
disabled: false | string;
/** If set, provides a "generate" button that fills the field with a random string matching this spec. */
generate: null | RandomString;
/** Whether the field value cannot be changed after initial configuration. */
immutable: boolean;
};
/** Spec for a multi-line textarea input field. */
export type ValueSpecTextarea = {
name: string;
description: string | null;
warning: string | null;
type: 'textarea';
/** Regex patterns used to validate the input value. */
patterns: Pattern[];
placeholder: string | null;
minLength: number | null;
maxLength: number | null;
/** Minimum number of visible rows. */
minRows: number;
/** Maximum number of visible rows before scrolling. */
maxRows: number;
required: boolean;
default: string | null;
disabled: false | string;
immutable: boolean;
};
/** Spec for a numeric input field. */
export type ValueSpecNumber = {
type: 'number';
/** Minimum allowed value, or `null` for unbounded. */
min: number | null;
/** Maximum allowed value, or `null` for unbounded. */
max: number | null;
/** Whether only whole numbers are accepted. */
integer: boolean;
/** Step increment for the input spinner, or `null` for any precision. */
step: number | null;
/** Display label for the unit (e.g. `"MB"`, `"seconds"`), shown next to the field. */
units: string | null;
placeholder: string | null;
name: string;
description: string | null;
warning: string | null;
required: boolean;
default: number | null;
disabled: false | string;
immutable: boolean;
};
/** Spec for a browser-native color picker field. */
export type ValueSpecColor = {
name: string;
description: string | null;
warning: string | null;
type: 'color';
required: boolean;
/** Default hex color string (e.g. `"#ff0000"`), or `null`. */
default: string | null;
disabled: false | string;
immutable: boolean;
};
/** Spec for a date, time, or datetime input field. */
export type ValueSpecDatetime = {
name: string;
description: string | null;
warning: string | null;
type: 'datetime';
required: boolean;
/** Controls which kind of picker is displayed. */
inputmode: 'date' | 'time' | 'datetime-local';
/** Minimum selectable date/time as an ISO string, or `null`. */
min: string | null;
/** Maximum selectable date/time as an ISO string, or `null`. */
max: string | null;
default: string | null;
disabled: false | string;
immutable: boolean;
};
/** Spec for a single-select field displayed as radio buttons in a modal. */
export type ValueSpecSelect = {
/** Map of option keys to display labels. */
values: Record<string, string>;
name: string;
description: string | null;
warning: string | null;
type: 'select';
default: string | null;
/** `false` if all enabled, a string disabling the whole field, or an array of disabled option keys. */
disabled: false | string | string[];
immutable: boolean;
};
/** Spec for a multi-select field displayed as checkboxes in a modal. */
export type ValueSpecMultiselect = {
/** Map of option keys to display labels. */
values: Record<string, string>;
name: string;
description: string | null;
warning: string | null;
type: 'multiselect';
/** Minimum number of selections required, or `null`. */
minLength: number | null;
/** Maximum number of selections allowed, or `null`. */
maxLength: number | null;
/** `false` if all enabled, a string disabling the whole field, or an array of disabled option keys. */
disabled: false | string | string[];
/** Array of option keys selected by default. */
default: string[];
immutable: boolean;
};
/** Spec for a boolean toggle (on/off switch). */
export type ValueSpecToggle = {
name: string;
description: string | null;
warning: string | null;
type: 'toggle';
default: boolean | null;
disabled: false | string;
immutable: boolean;
};
/**
* Spec for a discriminated union field — displays a dropdown for variant selection,
* and each variant can have its own nested sub-form.
*/
export type ValueSpecUnion = {
name: string;
description: string | null;
warning: string | null;
type: 'union';
/** Map of variant keys to their display name and nested form spec. */
variants: Record<string, {
/** Display name for this variant in the dropdown. */
name: string;
/** Nested form spec shown when this variant is selected. */
spec: InputSpec;
}>;
/** `false` if all enabled, a string disabling the whole field, or an array of disabled variant keys. */
disabled: false | string | string[];
default: string | null;
immutable: boolean;
};
/** Spec for a file upload input field. */
export type ValueSpecFile = {
name: string;
description: string | null;
warning: string | null;
type: 'file';
/** Allowed file extensions (e.g. `[".pem", ".crt"]`). */
extensions: string[];
required: boolean;
};
/** Spec for a collapsible grouping of nested fields (a "sub-form"). */
export type ValueSpecObject = {
name: string;
description: string | null;
warning: string | null;
type: 'object';
/** The nested form spec containing this object's fields. */
spec: InputSpec;
};
/** Spec for a hidden field — not displayed to the user but included in the form data. */
export type ValueSpecHidden = {
type: 'hidden';
};
/** The two supported list item types. */
export type ListValueSpecType = 'text' | 'object';
/** Maps a {@link ListValueSpecType} to its concrete list item spec. */
export type ListValueSpecOf<T extends ListValueSpecType> = T extends "text" ? ListValueSpecText : T extends "object" ? ListValueSpecObject : never;
/** A list field spec — union of text-list and object-list variants. */
export type ValueSpecList = ValueSpecListOf<ListValueSpecType>;
/**
* Spec for a list field — an interface to add, remove, and edit items in an ordered collection.
* The `spec` field determines whether list items are text strings or structured objects.
*/
export type ValueSpecListOf<T extends ListValueSpecType> = {
name: string;
description: string | null;
warning: string | null;
type: 'list';
/** The item spec — determines whether this is a list of text values or objects. */
spec: ListValueSpecOf<T>;
/** Minimum number of items, or `null` for no minimum. */
minLength: number | null;
/** Maximum number of items, or `null` for no maximum. */
maxLength: number | null;
disabled: false | string;
/** Default list items to populate on creation. */
default: string[] | DefaultString[] | Record<string, unknown>[] | readonly string[] | readonly DefaultString[] | readonly Record<string, unknown>[];
};
/** A regex validation pattern with a human-readable description of what it enforces. */
export type Pattern = {
/** The regex pattern string (without delimiters). */
regex: string;
/** A user-facing explanation shown when validation fails (e.g. `"Must be a valid email"`). */
description: string;
};
/** Spec for text items within a list field. */
export type ListValueSpecText = {
type: 'text';
patterns: Pattern[];
minLength: number | null;
maxLength: number | null;
masked: boolean;
generate: null | RandomString;
inputmode: 'text' | 'email' | 'tel' | 'url';
placeholder: string | null;
};
/** Spec for object items within a list field. */
export type ListValueSpecObject = {
type: 'object';
/** The form spec for each object item. */
spec: InputSpec;
/** Defines how uniqueness is determined among list items. */
uniqueBy: UniqueBy;
/** An expression used to generate the display string for each item in the list summary (e.g. a key path). */
displayAs: string | null;
};
/**
* Describes how list items determine uniqueness.
* - `null`: no uniqueness constraint
* - `string`: unique by a specific field key
* - `{ any: UniqueBy[] }`: unique if any of the sub-constraints match
* - `{ all: UniqueBy[] }`: unique if all sub-constraints match together
*/
export type UniqueBy = null | string | {
any: readonly UniqueBy[] | UniqueBy[];
} | {
all: readonly UniqueBy[] | UniqueBy[];
};
/** A default value that is either a literal string or a {@link RandomString} generation spec. */
export type DefaultString = string | RandomString;
/** Spec for generating a random string — used for default passwords, API keys, etc. */
export type RandomString = {
/** The character set to draw from (e.g. `"a-zA-Z0-9"`). */
charset: string;
/** The length of the generated string. */
len: number;
};
/**
* Type guard that narrows a {@link ValueSpec} to a {@link ValueSpecListOf} of a specific item type.
*
* @param t - The value spec to check
* @param s - The list item type to narrow to (`"text"` or `"object"`)
*/
export declare function isValueSpecListOf<S extends ListValueSpecType>(t: ValueSpec, s: S): t is ValueSpecListOf<S> & {
spec: ListValueSpecOf<S>;
};
@@ -0,0 +1,13 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isValueSpecListOf = isValueSpecListOf;
/**
* Type guard that narrows a {@link ValueSpec} to a {@link ValueSpecListOf} of a specific item type.
*
* @param t - The value spec to check
* @param s - The list item type to narrow to (`"text"` or `"object"`)
*/
function isValueSpecListOf(t, s) {
return 'spec' in t && t.spec.type === s;
}
//# sourceMappingURL=inputSpecTypes.js.map
@@ -0,0 +1 @@
{"version":3,"file":"inputSpecTypes.js","sourceRoot":"","sources":["../../../../../base/lib/actions/input/inputSpecTypes.ts"],"names":[],"mappings":";;AAoVA,8CAKC;AAXD;;;;;GAKG;AACH,SAAgB,iBAAiB,CAC/B,CAAY,EACZ,CAAI;IAEJ,OAAO,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,CAAA;AACzC,CAAC"}
+58
View File
@@ -0,0 +1,58 @@
import { InputSpec } from './input/builder';
import { ExtractInputSpecType } from './input/builder/inputSpec';
import * as T from '../types';
import { InitScript } from '../inits';
export type Run<A extends Record<string, any>> = (options: {
effects: T.Effects;
input: A;
spec: T.inputSpecTypes.InputSpec;
}) => Promise<(T.ActionResult & {
version: '1';
}) | null | void | undefined>;
export type GetInput<A extends Record<string, any>> = (options: {
effects: T.Effects;
prefill: T.DeepPartial<A> | null;
}) => Promise<null | void | undefined | T.DeepPartial<A>>;
export type MaybeFn<T, Opts = {
effects: T.Effects;
}> = T | ((options: Opts) => Promise<T>);
export interface ActionInfo<Id extends T.ActionId, Type extends Record<string, any>> {
readonly id: Id;
readonly _INPUT: Type;
}
export declare class Action<Id extends T.ActionId, Type extends Record<string, any>> implements ActionInfo<Id, Type> {
readonly id: Id;
private readonly metadataFn;
private readonly inputSpec;
private readonly getInputFn;
private readonly runFn;
readonly _INPUT: Type;
private prevInputSpec;
private constructor();
static withInput<Id extends T.ActionId, InputSpecType extends InputSpec<Record<string, any>>>(id: Id, metadata: MaybeFn<Omit<T.ActionMetadata, 'hasInput'>>, inputSpec: MaybeFn<InputSpecType, {
effects: T.Effects;
prefill: unknown | null;
}>, getInput: GetInput<ExtractInputSpecType<InputSpecType>>, run: Run<ExtractInputSpecType<InputSpecType>>): Action<Id, ExtractInputSpecType<InputSpecType>>;
static withoutInput<Id extends T.ActionId>(id: Id, metadata: MaybeFn<Omit<T.ActionMetadata, 'hasInput'>>, run: Run<{}>): Action<Id, {}>;
exportMetadata(options: {
effects: T.Effects;
}): Promise<T.ActionMetadata>;
getInput(options: {
effects: T.Effects;
prefill: T.DeepPartial<Type> | null;
}): Promise<T.ActionInput>;
run(options: {
effects: T.Effects;
input: Type;
}): Promise<T.ActionResult | null>;
}
export declare class Actions<AllActions extends Record<T.ActionId, Action<T.ActionId, any>>> implements InitScript {
private readonly actions;
private constructor();
static of(): Actions<{}>;
addAction<A extends Action<T.ActionId, any>>(action: A): Actions<AllActions & {
[id in A['id']]: A;
}>;
init(effects: T.Effects): Promise<void>;
get<Id extends T.ActionId>(actionId: Id): AllActions[Id];
}
+117
View File
@@ -0,0 +1,117 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Actions = exports.Action = void 0;
const util_1 = require("../util");
function callMaybeFn(maybeFn, options) {
if (maybeFn instanceof Function) {
return maybeFn(options);
}
else {
return Promise.resolve(maybeFn);
}
}
function mapMaybeFn(maybeFn, map) {
if (maybeFn instanceof Function) {
return async (...args) => map(await maybeFn(...args));
}
else {
return map(maybeFn);
}
}
class Action {
constructor(id, metadataFn, inputSpec, getInputFn, runFn) {
this.id = id;
this.metadataFn = metadataFn;
this.inputSpec = inputSpec;
this.getInputFn = getInputFn;
this.runFn = runFn;
this._INPUT = null;
this.prevInputSpec = {};
}
static withInput(id, metadata, inputSpec, getInput, run) {
return new Action(id, mapMaybeFn(metadata, (m) => ({ ...m, hasInput: true })), inputSpec, getInput, run);
}
static withoutInput(id, metadata, run) {
return new Action(id, mapMaybeFn(metadata, (m) => ({ ...m, hasInput: false })), null, async () => null, run);
}
async exportMetadata(options) {
const childEffects = options.effects.child(`setupActions/${this.id}`);
childEffects.constRetry = (0, util_1.once)(() => {
this.exportMetadata(options);
});
const metadata = await callMaybeFn(this.metadataFn, {
effects: childEffects,
});
await options.effects.action.export({ id: this.id, metadata });
return metadata;
}
async getInput(options) {
let spec = {};
if (this.inputSpec) {
const inputSpec = await callMaybeFn(this.inputSpec, options);
const built = await inputSpec?.build(options);
if (built) {
this.prevInputSpec[options.effects.eventId] = built;
spec = built.spec;
}
}
return {
eventId: options.effects.eventId,
spec,
value: (await this.getInputFn(options)) || null,
};
}
async run(options) {
let spec = {};
if (this.inputSpec) {
const prevInputSpec = this.prevInputSpec[options.effects.eventId];
if (!prevInputSpec) {
throw new Error(`getActionInput has not been called for EventID ${options.effects.eventId}`);
}
options.input = prevInputSpec.validator.parse(options.input);
spec = prevInputSpec.spec;
}
return ((await this.runFn({
effects: options.effects,
input: options.input,
spec,
})) ?? null);
}
}
exports.Action = Action;
class Actions {
constructor(actions) {
this.actions = actions;
}
static of() {
return new Actions({});
}
addAction(action) {
return new Actions({ ...this.actions, [action.id]: action });
}
async init(effects) {
for (let action of Object.values(this.actions)) {
const fn = async () => {
let res = () => { };
const complete = new Promise((resolve) => {
res = resolve;
});
const e = effects.child(action.id);
e.constRetry = (0, util_1.once)(() => complete.then(() => fn()).catch(console.error));
try {
await action.exportMetadata({ effects: e });
}
finally {
res();
}
};
await fn();
}
await effects.action.clear({ except: Object.keys(this.actions) });
}
get(actionId) {
return this.actions[actionId];
}
}
exports.Actions = Actions;
//# sourceMappingURL=setupActions.js.map
@@ -0,0 +1 @@
{"version":3,"file":"setupActions.js","sourceRoot":"","sources":["../../../../base/lib/actions/setupActions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,87 @@
/* eslint-disable */
var jumpToCode = (function init() {
// Classes of code we would like to highlight in the file view
var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no'];
// Elements to highlight in the file listing view
var fileListingElements = ['td.pct.low'];
// We don't want to select elements that are direct descendants of another match
var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > `
// Selecter that finds elements on the page to which we can jump
var selector =
fileListingElements.join(', ') +
', ' +
notSelector +
missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b`
// The NodeList of matching elements
var missingCoverageElements = document.querySelectorAll(selector);
var currentIndex;
function toggleClass(index) {
missingCoverageElements
.item(currentIndex)
.classList.remove('highlighted');
missingCoverageElements.item(index).classList.add('highlighted');
}
function makeCurrent(index) {
toggleClass(index);
currentIndex = index;
missingCoverageElements.item(index).scrollIntoView({
behavior: 'smooth',
block: 'center',
inline: 'center'
});
}
function goToPrevious() {
var nextIndex = 0;
if (typeof currentIndex !== 'number' || currentIndex === 0) {
nextIndex = missingCoverageElements.length - 1;
} else if (missingCoverageElements.length > 1) {
nextIndex = currentIndex - 1;
}
makeCurrent(nextIndex);
}
function goToNext() {
var nextIndex = 0;
if (
typeof currentIndex === 'number' &&
currentIndex < missingCoverageElements.length - 1
) {
nextIndex = currentIndex + 1;
}
makeCurrent(nextIndex);
}
return function jump(event) {
if (
document.getElementById('fileSearch') === document.activeElement &&
document.activeElement != null
) {
// if we're currently focused on the search input, we don't want to navigate
return;
}
switch (event.which) {
case 78: // n
case 74: // j
goToNext();
break;
case 66: // b
case 75: // k
case 80: // p
goToPrevious();
break;
}
};
})();
window.addEventListener('keydown', jumpToCode);
File diff suppressed because one or more lines are too long
@@ -0,0 +1,196 @@
/* eslint-disable */
var addSorting = (function() {
'use strict';
var cols,
currentSort = {
index: 0,
desc: false
};
// returns the summary table element
function getTable() {
return document.querySelector('.coverage-summary');
}
// returns the thead element of the summary table
function getTableHeader() {
return getTable().querySelector('thead tr');
}
// returns the tbody element of the summary table
function getTableBody() {
return getTable().querySelector('tbody');
}
// returns the th element for nth column
function getNthColumn(n) {
return getTableHeader().querySelectorAll('th')[n];
}
function onFilterInput() {
const searchValue = document.getElementById('fileSearch').value;
const rows = document.getElementsByTagName('tbody')[0].children;
for (let i = 0; i < rows.length; i++) {
const row = rows[i];
if (
row.textContent
.toLowerCase()
.includes(searchValue.toLowerCase())
) {
row.style.display = '';
} else {
row.style.display = 'none';
}
}
}
// loads the search box
function addSearchBox() {
var template = document.getElementById('filterTemplate');
var templateClone = template.content.cloneNode(true);
templateClone.getElementById('fileSearch').oninput = onFilterInput;
template.parentElement.appendChild(templateClone);
}
// loads all columns
function loadColumns() {
var colNodes = getTableHeader().querySelectorAll('th'),
colNode,
cols = [],
col,
i;
for (i = 0; i < colNodes.length; i += 1) {
colNode = colNodes[i];
col = {
key: colNode.getAttribute('data-col'),
sortable: !colNode.getAttribute('data-nosort'),
type: colNode.getAttribute('data-type') || 'string'
};
cols.push(col);
if (col.sortable) {
col.defaultDescSort = col.type === 'number';
colNode.innerHTML =
colNode.innerHTML + '<span class="sorter"></span>';
}
}
return cols;
}
// attaches a data attribute to every tr element with an object
// of data values keyed by column name
function loadRowData(tableRow) {
var tableCols = tableRow.querySelectorAll('td'),
colNode,
col,
data = {},
i,
val;
for (i = 0; i < tableCols.length; i += 1) {
colNode = tableCols[i];
col = cols[i];
val = colNode.getAttribute('data-value');
if (col.type === 'number') {
val = Number(val);
}
data[col.key] = val;
}
return data;
}
// loads all row data
function loadData() {
var rows = getTableBody().querySelectorAll('tr'),
i;
for (i = 0; i < rows.length; i += 1) {
rows[i].data = loadRowData(rows[i]);
}
}
// sorts the table using the data for the ith column
function sortByIndex(index, desc) {
var key = cols[index].key,
sorter = function(a, b) {
a = a.data[key];
b = b.data[key];
return a < b ? -1 : a > b ? 1 : 0;
},
finalSorter = sorter,
tableBody = document.querySelector('.coverage-summary tbody'),
rowNodes = tableBody.querySelectorAll('tr'),
rows = [],
i;
if (desc) {
finalSorter = function(a, b) {
return -1 * sorter(a, b);
};
}
for (i = 0; i < rowNodes.length; i += 1) {
rows.push(rowNodes[i]);
tableBody.removeChild(rowNodes[i]);
}
rows.sort(finalSorter);
for (i = 0; i < rows.length; i += 1) {
tableBody.appendChild(rows[i]);
}
}
// removes sort indicators for current column being sorted
function removeSortIndicators() {
var col = getNthColumn(currentSort.index),
cls = col.className;
cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, '');
col.className = cls;
}
// adds sort indicators for current column being sorted
function addSortIndicators() {
getNthColumn(currentSort.index).className += currentSort.desc
? ' sorted-desc'
: ' sorted';
}
// adds event listeners for all sorter widgets
function enableUI() {
var i,
el,
ithSorter = function ithSorter(i) {
var col = cols[i];
return function() {
var desc = col.defaultDescSort;
if (currentSort.index === i) {
desc = !currentSort.desc;
}
sortByIndex(i, desc);
removeSortIndicators();
currentSort.index = i;
currentSort.desc = desc;
addSortIndicators();
};
};
for (i = 0; i < cols.length; i += 1) {
if (cols[i].sortable) {
// add the click event handler on the th so users
// dont have to click on those tiny arrows
el = getNthColumn(i).querySelector('.sorter').parentElement;
if (el.addEventListener) {
el.addEventListener('click', ithSorter(i));
} else {
el.attachEvent('onclick', ithSorter(i));
}
}
}
}
// adds sorting functionality to the UI
return function() {
if (!getTable()) {
return;
}
cols = loadColumns();
loadData();
addSearchBox();
addSortIndicators();
enableUI();
};
})();
window.addEventListener('load', addSorting);
@@ -0,0 +1,21 @@
import { PackageId, HealthCheckId, DependencyRequirement, CheckDependenciesResult } from '../types';
import { Effects } from '../Effects';
export type CheckDependencies<DependencyId extends PackageId = PackageId> = {
infoFor: (packageId: DependencyId) => {
requirement: DependencyRequirement;
result: CheckDependenciesResult;
};
installedSatisfied: (packageId: DependencyId) => boolean;
installedVersionSatisfied: (packageId: DependencyId) => boolean;
runningSatisfied: (packageId: DependencyId) => boolean;
tasksSatisfied: (packageId: DependencyId) => boolean;
healthCheckSatisfied: (packageId: DependencyId, healthCheckId: HealthCheckId) => boolean;
satisfied: () => boolean;
throwIfInstalledNotSatisfied: (packageId: DependencyId) => null;
throwIfInstalledVersionNotSatisfied: (packageId: DependencyId) => null;
throwIfRunningNotSatisfied: (packageId: DependencyId) => null;
throwIfTasksNotSatisfied: (packageId: DependencyId) => null;
throwIfHealthNotSatisfied: (packageId: DependencyId, healthCheckId?: HealthCheckId) => null;
throwIfNotSatisfied: (packageId?: DependencyId) => null;
};
export declare function checkDependencies<DependencyId extends PackageId = PackageId>(effects: Effects, packageIds?: DependencyId[]): Promise<CheckDependencies<DependencyId>>;
@@ -0,0 +1,156 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.checkDependencies = checkDependencies;
const exver_1 = require("../exver");
async function checkDependencies(effects, packageIds) {
let [dependencies, results] = await Promise.all([
effects.getDependencies(),
effects.checkDependencies({
packageIds,
}),
]);
if (packageIds) {
dependencies = dependencies.filter((d) => packageIds.includes(d.id));
}
const infoFor = (packageId) => {
const dependencyRequirement = dependencies.find((d) => d.id === packageId);
const dependencyResult = results.find((d) => d.packageId === packageId);
if (!dependencyRequirement || !dependencyResult) {
throw new Error(`Unknown DependencyId ${packageId}`);
}
return { requirement: dependencyRequirement, result: dependencyResult };
};
const installedSatisfied = (packageId) => !!infoFor(packageId).result.installedVersion;
const installedVersionSatisfied = (packageId) => {
const dep = infoFor(packageId);
return (!!dep.result.installedVersion &&
exver_1.ExtendedVersion.parse(dep.result.installedVersion).satisfies(exver_1.VersionRange.parse(dep.requirement.versionRange)));
};
const runningSatisfied = (packageId) => {
const dep = infoFor(packageId);
return dep.requirement.kind !== 'running' || dep.result.isRunning;
};
const tasksSatisfied = (packageId) => Object.entries(infoFor(packageId).result.tasks).filter(([_, t]) => t?.active && t.task.severity === 'critical').length === 0;
const healthCheckSatisfied = (packageId, healthCheckId) => {
const dep = infoFor(packageId);
if (healthCheckId &&
(dep.requirement.kind !== 'running' ||
!dep.requirement.healthChecks.includes(healthCheckId))) {
throw new Error(`Unknown HealthCheckId ${healthCheckId}`);
}
const errors = dep.requirement.kind === 'running'
? dep.requirement.healthChecks
.map((id) => [id, dep.result.healthChecks[id] ?? null])
.filter(([id, _]) => (healthCheckId ? id === healthCheckId : true))
.filter(([_, res]) => res?.result !== 'success')
: [];
return errors.length === 0;
};
const pkgSatisfied = (packageId) => installedSatisfied(packageId) &&
installedVersionSatisfied(packageId) &&
runningSatisfied(packageId) &&
tasksSatisfied(packageId) &&
healthCheckSatisfied(packageId);
const satisfied = (packageId) => packageId
? pkgSatisfied(packageId)
: dependencies.every((d) => pkgSatisfied(d.id));
const throwIfInstalledNotSatisfied = (packageId) => {
const dep = infoFor(packageId);
if (!dep.result.installedVersion) {
throw new Error(`${dep.result.title || packageId} is not installed`);
}
return null;
};
const throwIfInstalledVersionNotSatisfied = (packageId) => {
const dep = infoFor(packageId);
if (!dep.result.installedVersion) {
throw new Error(`${dep.result.title || packageId} is not installed`);
}
if (![dep.result.installedVersion, ...dep.result.satisfies].find((v) => exver_1.ExtendedVersion.parse(v).satisfies(exver_1.VersionRange.parse(dep.requirement.versionRange)))) {
throw new Error(`Installed version ${dep.result.installedVersion} of ${dep.result.title || packageId} does not match expected version range ${dep.requirement.versionRange}`);
}
return null;
};
const throwIfRunningNotSatisfied = (packageId) => {
const dep = infoFor(packageId);
if (dep.requirement.kind === 'running' && !dep.result.isRunning) {
throw new Error(`${dep.result.title || packageId} is not running`);
}
return null;
};
const throwIfTasksNotSatisfied = (packageId) => {
const dep = infoFor(packageId);
const reqs = Object.entries(dep.result.tasks)
.filter(([_, t]) => t?.active && t.task.severity === 'critical')
.map(([id, _]) => id);
if (reqs.length) {
throw new Error(`The following action requests have not been fulfilled: ${reqs.join(', ')}`);
}
return null;
};
const throwIfHealthNotSatisfied = (packageId, healthCheckId) => {
const dep = infoFor(packageId);
if (healthCheckId &&
(dep.requirement.kind !== 'running' ||
!dep.requirement.healthChecks.includes(healthCheckId))) {
throw new Error(`Unknown HealthCheckId ${healthCheckId}`);
}
const errors = dep.requirement.kind === 'running'
? dep.requirement.healthChecks
.map((id) => [id, dep.result.healthChecks[id] ?? null])
.filter(([id, _]) => (healthCheckId ? id === healthCheckId : true))
.filter(([_, res]) => res?.result !== 'success')
: [];
if (errors.length) {
throw new Error(errors
.map(([id, e]) => e
? `Health Check ${e.name} of ${dep.result.title || packageId} failed with status ${e.result}${e.message ? `: ${e.message}` : ''}`
: `Health Check ${id} of ${dep.result.title} does not exist`)
.join('; '));
}
return null;
};
const throwIfPkgNotSatisfied = (packageId) => {
throwIfInstalledNotSatisfied(packageId);
throwIfInstalledVersionNotSatisfied(packageId);
throwIfRunningNotSatisfied(packageId);
throwIfTasksNotSatisfied(packageId);
throwIfHealthNotSatisfied(packageId);
return null;
};
const throwIfNotSatisfied = (packageId) => packageId
? throwIfPkgNotSatisfied(packageId)
: (() => {
const err = dependencies.flatMap((d) => {
try {
throwIfPkgNotSatisfied(d.id);
}
catch (e) {
if (e instanceof Error)
return [e.message];
throw e;
}
return [];
});
if (err.length) {
throw new Error(err.join('; '));
}
return null;
})();
return {
infoFor,
installedSatisfied,
installedVersionSatisfied,
runningSatisfied,
tasksSatisfied,
healthCheckSatisfied,
satisfied,
throwIfInstalledNotSatisfied,
throwIfInstalledVersionNotSatisfied,
throwIfRunningNotSatisfied,
throwIfTasksNotSatisfied,
throwIfHealthNotSatisfied,
throwIfNotSatisfied,
};
}
//# sourceMappingURL=dependencies.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,22 @@
import * as T from '../types';
export type RequiredDependenciesOf<Manifest extends T.SDKManifest> = {
[K in keyof Manifest['dependencies']]: Exclude<Manifest['dependencies'][K], undefined>['optional'] extends false ? K : never;
}[keyof Manifest['dependencies']];
export type OptionalDependenciesOf<Manifest extends T.SDKManifest> = Exclude<keyof Manifest['dependencies'], RequiredDependenciesOf<Manifest>>;
type DependencyRequirement = {
kind: 'running';
healthChecks: Array<T.HealthCheckId>;
versionRange: string;
} | {
kind: 'exists';
versionRange: string;
};
export type CurrentDependenciesResult<Manifest extends T.SDKManifest> = {
[K in RequiredDependenciesOf<Manifest>]: DependencyRequirement;
} & {
[K in OptionalDependenciesOf<Manifest>]?: DependencyRequirement;
};
export declare function setupDependencies<Manifest extends T.SDKManifest>(fn: (options: {
effects: T.Effects;
}) => Promise<CurrentDependenciesResult<Manifest>>): (effects: T.Effects) => Promise<null>;
export {};
@@ -0,0 +1,19 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.setupDependencies = setupDependencies;
const _checkType = null;
function setupDependencies(fn) {
return async (effects) => {
const dependencyType = await fn({ effects });
return await effects.setDependencies({
dependencies: Object.entries(dependencyType)
.map(([k, v]) => [k, v])
.map(([id, { versionRange, ...x }]) => ({
id,
...x,
versionRange: versionRange.toString(),
})),
});
};
}
//# sourceMappingURL=setupDependencies.js.map
@@ -0,0 +1 @@
{"version":3,"file":"setupDependencies.js","sourceRoot":"","sources":["../../../../base/lib/dependencies/setupDependencies.ts"],"names":[],"mappings":";;AAsCA,8CAoBC;AA/BD,MAAM,UAAU,GAGZ,IAAI,CAAA;AAQR,SAAgB,iBAAiB,CAC/B,EAEkD;IAElD,OAAO,KAAK,EAAE,OAAkB,EAAE,EAAE;QAClC,MAAM,cAAc,GAAG,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,CAAA;QAC5C,OAAO,MAAM,OAAO,CAAC,eAAe,CAAC;YACnC,YAAY,EAAE,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC;iBACzC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAA0B,CAAU,CAAC;iBACzD,GAAG,CACF,CAAC,CAAC,EAAE,EAAE,EAAE,YAAY,EAAE,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAC/B,CAAC;gBACC,EAAE;gBACF,GAAG,CAAC;gBACJ,YAAY,EAAE,YAAY,CAAC,QAAQ,EAAE;aACtC,CAA4B,CAChC;SACJ,CAAC,CAAA;IACJ,CAAC,CAAA;AACH,CAAC"}
+156
View File
@@ -0,0 +1,156 @@
export interface FilePosition {
offset: number;
line: number;
column: number;
}
export interface FileRange {
start: FilePosition;
end: FilePosition;
source: string;
}
export interface LiteralExpectation {
type: "literal";
text: string;
ignoreCase: boolean;
}
export interface ClassParts extends Array<string | ClassParts> {
}
export interface ClassExpectation {
type: "class";
parts: ClassParts;
inverted: boolean;
ignoreCase: boolean;
}
export interface AnyExpectation {
type: "any";
}
export interface EndExpectation {
type: "end";
}
export interface OtherExpectation {
type: "other";
description: string;
}
export type Expectation = LiteralExpectation | ClassExpectation | AnyExpectation | EndExpectation | OtherExpectation;
declare class _PeggySyntaxError extends Error {
static buildMessage(expected: Expectation[], found: string | null): string;
message: string;
expected: Expectation[];
found: string | null;
location: FileRange;
name: string;
constructor(message: string, expected: Expectation[], found: string | null, location: FileRange);
format(sources: {
source?: any;
text: string;
}[]): string;
}
export interface TraceEvent {
type: string;
rule: string;
result?: any;
location: FileRange;
}
export interface ParseOptions {
filename?: string;
startRule?: "VersionRange" | "Or" | "And" | "VersionRangeAtom" | "Parens" | "Anchor" | "VersionSpec" | "FlavorAtom" | "Not" | "Any" | "None" | "CmpOp" | "ExtendedVersion" | "EmverVersionRange" | "EmverVersionRangeAtom" | "EmverParens" | "EmverAnchor" | "EmverNot" | "Emver" | "Flavor" | "FlavorString" | "String" | "Version" | "PreRelease" | "PreReleaseSegment" | "VersionNumber" | "Digit" | "_";
tracer?: any;
[key: string]: any;
}
export type ParseFunction = <Options extends ParseOptions>(input: string, options?: Options) => Options extends {
startRule: infer StartRule;
} ? StartRule extends "VersionRange" ? VersionRange : StartRule extends "Or" ? Or : StartRule extends "And" ? And : StartRule extends "VersionRangeAtom" ? VersionRangeAtom : StartRule extends "Parens" ? Parens : StartRule extends "Anchor" ? Anchor : StartRule extends "VersionSpec" ? VersionSpec : StartRule extends "FlavorAtom" ? FlavorAtom : StartRule extends "Not" ? Not : StartRule extends "Any" ? Any : StartRule extends "None" ? None : StartRule extends "CmpOp" ? CmpOp : StartRule extends "ExtendedVersion" ? ExtendedVersion : StartRule extends "EmverVersionRange" ? EmverVersionRange : StartRule extends "EmverVersionRangeAtom" ? EmverVersionRangeAtom : StartRule extends "EmverParens" ? EmverParens : StartRule extends "EmverAnchor" ? EmverAnchor : StartRule extends "EmverNot" ? EmverNot : StartRule extends "Emver" ? Emver : StartRule extends "Flavor" ? Flavor : StartRule extends "FlavorString" ? FlavorString : StartRule extends "String" ? String_1 : StartRule extends "Version" ? Version : StartRule extends "PreRelease" ? PreRelease : StartRule extends "PreReleaseSegment" ? PreReleaseSegment : StartRule extends "VersionNumber" ? VersionNumber : StartRule extends "Digit" ? Digit : StartRule extends "_" ? _ : VersionRange : VersionRange;
export declare const parse: ParseFunction;
export declare const PeggySyntaxError: typeof _PeggySyntaxError;
export type PeggySyntaxError = _PeggySyntaxError;
export type VersionRange = [
VersionRangeAtom,
[
_,
[Or | And, _] | null,
VersionRangeAtom
][]
];
export type Or = "||";
export type And = "&&";
export type VersionRangeAtom = Parens | Anchor | Not | Any | None | FlavorAtom;
export type Parens = {
type: "Parens";
expr: VersionRange;
};
export type Anchor = {
type: "Anchor";
operator: CmpOp | null;
version: VersionSpec;
};
export type VersionSpec = {
flavor: NonNullable<Flavor | null> | null;
upstream: Version;
downstream: any;
};
export type FlavorAtom = {
type: "Flavor";
flavor: FlavorString;
};
export type Not = {
type: "Not";
value: VersionRangeAtom;
};
export type Any = {
type: "Any";
};
export type None = {
type: "None";
};
export type CmpOp = ">=" | "<=" | ">" | "<" | "=" | "!=" | "^" | "~";
export type ExtendedVersion = {
flavor: NonNullable<Flavor | null> | null;
upstream: Version;
downstream: Version;
};
export type EmverVersionRange = [
EmverVersionRangeAtom,
[
_,
[Or | And, _] | null,
EmverVersionRangeAtom
][]
];
export type EmverVersionRangeAtom = EmverParens | EmverAnchor | EmverNot | Any | None;
export type EmverParens = {
type: "Parens";
expr: EmverVersionRange;
};
export type EmverAnchor = {
type: "Anchor";
operator: CmpOp | null;
version: Emver;
};
export type EmverNot = {
type: "Not";
value: EmverVersionRangeAtom;
};
export type Emver = {
flavor: null;
upstream: {
number: [Digit, Digit, Digit];
prerelease: [];
};
downstream: {
number: [0 | NonNullable<Digit | null>];
prerelease: [];
};
};
export type Flavor = FlavorString;
export type FlavorString = string;
export type String_1 = string;
export type Version = {
number: VersionNumber;
prerelease: never[] | NonNullable<PreRelease | null>;
};
export type PreRelease = PreReleaseSegment[];
export type PreReleaseSegment = Digit | String_1;
export type VersionNumber = Digit[];
export type Digit = number;
export type _ = string[];
export {};
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+389
View File
@@ -0,0 +1,389 @@
import { DeepMap } from 'deep-equality-data-structures';
import * as P from './exver';
/**
* Compile-time utility type that validates a version string literal conforms to semver format.
*
* Resolves to `unknown` if valid, `never` if invalid. Used with {@link testTypeVersion}.
*
* @example
* ```ts
* type Valid = ValidateVersion<"1.2.3"> // unknown (valid)
* type Invalid = ValidateVersion<"-3"> // never (invalid)
* ```
*/
export type ValidateVersion<T extends String> = T extends `-${infer A}` ? never : T extends `${infer A}-${string}` ? ValidateVersion<A> : T extends `${bigint}` ? unknown : T extends `${bigint}.${infer A}` ? ValidateVersion<A> : never;
/**
* Compile-time utility type that validates an extended version string literal.
*
* Extended versions have the format `upstream:downstream` or `#flavor:upstream:downstream`.
*
* @example
* ```ts
* type Valid = ValidateExVer<"1.2.3:0"> // valid
* type Flavored = ValidateExVer<"#bitcoin:1.0:0"> // valid
* type Bad = ValidateExVer<"1.2-3"> // never (invalid)
* ```
*/
export type ValidateExVer<T extends string> = T extends `#${string}:${infer A}:${infer B}` ? ValidateVersion<A> & ValidateVersion<B> : T extends `${infer A}:${infer B}` ? ValidateVersion<A> & ValidateVersion<B> : never;
/**
* Validates a tuple of extended version string literals at compile time.
*
* @example
* ```ts
* type Valid = ValidateExVers<["1.0:0", "2.0:0"]> // valid
* ```
*/
export type ValidateExVers<T> = T extends [] ? unknown[] : T extends [infer A, ...infer B] ? ValidateExVer<A & string> & ValidateExVers<B> : never[];
type Anchor = {
type: 'Anchor';
operator: P.CmpOp;
version: ExtendedVersion;
};
type And = {
type: 'And';
left: VersionRange;
right: VersionRange;
};
type Or = {
type: 'Or';
left: VersionRange;
right: VersionRange;
};
type Not = {
type: 'Not';
value: VersionRange;
};
type Flavor = {
type: 'Flavor';
flavor: string | null;
};
type FlavorNot = {
type: 'FlavorNot';
flavors: Set<string | null>;
};
type FlavorAtom = Flavor | FlavorNot;
/**
* Splits a number line of versions in half, so that every possible semver is either to the left or right.
* The `side` field handles inclusively.
*
* # Example
* Consider the version `1.2.3`. For side=-1 the version point is like `1.2.2.999*.999*.**` (that is, 1.2.3.0.0.** is greater) and
* for side=+1 the point is like `1.2.3.0.0.**.1` (that is, 1.2.3.0.0.** is less).
*/
type VersionRangePoint = {
upstream: Version;
downstream: Version;
side: -1 | 1;
};
/**
* Truth tables for version numbers and flavors. For each flavor we need a separate table, which
* is quite straightforward. But in order to exhaustively enumerate the boolean values of every
* combination of flavors and versions we also need tables for flavor negations.
*/
type VersionRangeTables = DeepMap<FlavorAtom, VersionRangeTable> | boolean;
/**
* A truth table for version numbers. This is easiest to picture as a number line, cut up into
* ranges of versions between version points.
*/
declare class VersionRangeTable {
protected points: Array<VersionRangePoint>;
protected values: boolean[];
private constructor();
static zip(a: VersionRangeTable, b: VersionRangeTable, func: (a: boolean, b: boolean) => boolean): VersionRangeTable;
/**
* Creates a version table which is `true` for the given flavor, and `false` for any other flavor.
*/
static eqFlavor(flavor: string | null): VersionRangeTables;
/**
* Creates a version table with exactly two ranges (to the left and right of the given point) and with `false` for any other flavor.
* This is easiest to understand by looking at `VersionRange.tables`.
*/
static cmpPoint(flavor: string | null, point: VersionRangePoint, left: boolean, right: boolean): VersionRangeTables;
/**
* Helper for `cmpPoint`.
*/
static cmp(version: ExtendedVersion, side: -1 | 1, left: boolean, right: boolean): VersionRangeTables;
static not(tables: VersionRangeTables): boolean | DeepMap<FlavorAtom, VersionRangeTable, FlavorAtom, VersionRangeTable>;
static and(a_tables: VersionRangeTables, b_tables: VersionRangeTables): VersionRangeTables;
static or(...in_tables: VersionRangeTables[]): VersionRangeTables;
/**
* If this is true for all versions or false for all versions, returen that value. Otherwise return null.
*/
static collapse(tables: VersionRangeTables): boolean | null;
/**
* Expresses this truth table as a series of version range operators.
* https://en.wikipedia.org/wiki/Canonical_normal_form#Minterms
*/
static minterms(tables: VersionRangeTables): VersionRange;
}
/**
* Represents a parsed version range expression used to match against {@link Version} or {@link ExtendedVersion} values.
*
* Version ranges support standard comparison operators (`=`, `>`, `<`, `>=`, `<=`, `!=`),
* caret (`^`) and tilde (`~`) ranges, boolean logic (`&&`, `||`, `!`), and flavor matching (`#flavor`).
*
* @example
* ```ts
* const range = VersionRange.parse(">=1.0.0:0 && <2.0.0:0")
* const version = ExtendedVersion.parse("1.5.0:0")
* console.log(range.satisfiedBy(version)) // true
*
* // Combine ranges with boolean logic
* const combined = VersionRange.and(
* VersionRange.parse(">=1.0:0"),
* VersionRange.parse("<3.0:0"),
* )
*
* // Match a specific flavor
* const flavored = VersionRange.parse("#bitcoin")
* ```
*/
export declare class VersionRange {
atom: Anchor | And | Or | Not | P.Any | P.None | Flavor;
constructor(atom: Anchor | And | Or | Not | P.Any | P.None | Flavor);
toStringParens(parent: 'And' | 'Or' | 'Not'): string;
/** Serializes this version range back to its canonical string representation. */
toString(): string;
private static parseAtom;
private static parseRange;
/**
* Parses a version range string into a `VersionRange`.
*
* @param range - A version range expression, e.g. `">=1.0.0:0 && <2.0.0:0"`, `"^1.2:0"`, `"*"`
* @returns The parsed `VersionRange`
* @throws If the string is not a valid version range expression
*/
static parse(range: string): VersionRange;
/**
* Creates a version range from a comparison operator and an {@link ExtendedVersion}.
*
* @param operator - One of `"="`, `">"`, `"<"`, `">="`, `"<="`, `"!="`, `"^"`, `"~"`
* @param version - The version to compare against
*/
static anchor(operator: P.CmpOp, version: ExtendedVersion): VersionRange;
/**
* Creates a version range that matches only versions with the specified flavor.
*
* @param flavor - The flavor string to match, or `null` for the default (unflavored) variant
*/
static flavor(flavor: string | null): VersionRange;
/**
* Parses a legacy "emver" format version range string.
*
* @param range - A version range in the legacy emver format
* @returns The parsed `VersionRange`
*/
static parseEmver(range: string): VersionRange;
/** Returns the intersection of this range with another (logical AND). */
and(right: VersionRange): VersionRange;
/** Returns the union of this range with another (logical OR). */
or(right: VersionRange): VersionRange;
/** Returns the negation of this range (logical NOT). */
not(): VersionRange;
/**
* Returns the logical AND (intersection) of multiple version ranges.
* Short-circuits on `none()` and skips `any()`.
*/
static and(...xs: Array<VersionRange>): VersionRange;
/**
* Returns the logical OR (union) of multiple version ranges.
* Short-circuits on `any()` and skips `none()`.
*/
static or(...xs: Array<VersionRange>): VersionRange;
/** Returns a version range that matches all versions (wildcard `*`). */
static any(): VersionRange;
/** Returns a version range that matches no versions (`!`). */
static none(): VersionRange;
/**
* Returns `true` if the given version satisfies this range.
*
* @param version - A {@link Version} or {@link ExtendedVersion} to test
*/
satisfiedBy(version: Version | ExtendedVersion): boolean;
tables(): VersionRangeTables;
/** Returns `true` if any version exists that could satisfy this range. */
satisfiable(): boolean;
/** Returns `true` if this range and `other` share at least one satisfying version. */
intersects(other: VersionRange): boolean;
/**
* Returns a canonical (simplified) form of this range using minterm expansion.
* Useful for normalizing complex boolean expressions into a minimal representation.
*/
normalize(): VersionRange;
}
/**
* Represents a semantic version number with numeric segments and optional prerelease identifiers.
*
* Follows semver precedence rules: numeric segments are compared left-to-right,
* and a version with prerelease identifiers has lower precedence than the same version without.
*
* @example
* ```ts
* const v = Version.parse("1.2.3")
* console.log(v.toString()) // "1.2.3"
* console.log(v.compare(Version.parse("1.3.0"))) // "less"
*
* const pre = Version.parse("2.0.0-beta.1")
* console.log(pre.compare(Version.parse("2.0.0"))) // "less" (prerelease < release)
* ```
*/
export declare class Version {
/** The numeric version segments (e.g. `[1, 2, 3]` for `"1.2.3"`). */
number: number[];
/** Optional prerelease identifiers (e.g. `["beta", 1]` for `"-beta.1"`). */
prerelease: (string | number)[];
constructor(
/** The numeric version segments (e.g. `[1, 2, 3]` for `"1.2.3"`). */
number: number[],
/** Optional prerelease identifiers (e.g. `["beta", 1]` for `"-beta.1"`). */
prerelease: (string | number)[]);
/** Serializes this version to its string form (e.g. `"1.2.3"` or `"1.0.0-beta.1"`). */
toString(): string;
/**
* Compares this version against another using semver precedence rules.
*
* @param other - The version to compare against
* @returns `'greater'`, `'equal'`, or `'less'`
*/
compare(other: Version): 'greater' | 'equal' | 'less';
/**
* Compares two versions, returning a numeric value suitable for use with `Array.sort()`.
*
* @returns `-1` if less, `0` if equal, `1` if greater
*/
compareForSort(other: Version): -1 | 0 | 1;
/**
* Parses a version string into a `Version` instance.
*
* @param version - A semver-compatible string, e.g. `"1.2.3"` or `"1.0.0-beta.1"`
* @throws If the string is not a valid version
*/
static parse(version: string): Version;
/**
* Returns `true` if this version satisfies the given {@link VersionRange}.
* Internally treats this as an unflavored {@link ExtendedVersion} with downstream `0`.
*/
satisfies(versionRange: VersionRange): boolean;
}
/**
* Represents an extended version with an optional flavor, an upstream version, and a downstream version.
*
* The format is `#flavor:upstream:downstream` (e.g. `#bitcoin:1.2.3:0`) or `upstream:downstream`
* for unflavored versions. Flavors allow multiple variants of a package to coexist.
*
* - **flavor**: An optional string identifier for the variant (e.g. `"bitcoin"`, `"litecoin"`)
* - **upstream**: The version of the upstream software being packaged
* - **downstream**: The version of the StartOS packaging itself
*
* Versions with different flavors are incomparable (comparison returns `null`).
*
* @example
* ```ts
* const v = ExtendedVersion.parse("#bitcoin:1.2.3:0")
* console.log(v.flavor) // "bitcoin"
* console.log(v.upstream) // Version { number: [1, 2, 3] }
* console.log(v.downstream) // Version { number: [0] }
* console.log(v.toString()) // "#bitcoin:1.2.3:0"
*
* const range = VersionRange.parse(">=1.0.0:0")
* console.log(v.satisfies(range)) // true
* ```
*/
export declare class ExtendedVersion {
/** The flavor identifier (e.g. `"bitcoin"`), or `null` for unflavored versions. */
flavor: string | null;
/** The upstream software version. */
upstream: Version;
/** The downstream packaging version. */
downstream: Version;
constructor(
/** The flavor identifier (e.g. `"bitcoin"`), or `null` for unflavored versions. */
flavor: string | null,
/** The upstream software version. */
upstream: Version,
/** The downstream packaging version. */
downstream: Version);
/** Serializes this extended version to its string form (e.g. `"#bitcoin:1.2.3:0"` or `"1.0.0:1"`). */
toString(): string;
/**
* Compares this extended version against another.
*
* @returns `'greater'`, `'equal'`, `'less'`, or `null` if the flavors differ (incomparable)
*/
compare(other: ExtendedVersion): 'greater' | 'equal' | 'less' | null;
/**
* Lexicographic comparison — compares flavors alphabetically first, then versions.
* Unlike {@link compare}, this never returns `null`: different flavors are ordered alphabetically.
*/
compareLexicographic(other: ExtendedVersion): 'greater' | 'equal' | 'less';
/**
* Returns a numeric comparison result suitable for use with `Array.sort()`.
* Uses lexicographic ordering (flavors sorted alphabetically, then by version).
*/
compareForSort(other: ExtendedVersion): 1 | 0 | -1;
/** Returns `true` if this version is strictly greater than `other`. Returns `false` if flavors differ. */
greaterThan(other: ExtendedVersion): boolean;
/** Returns `true` if this version is greater than or equal to `other`. Returns `false` if flavors differ. */
greaterThanOrEqual(other: ExtendedVersion): boolean;
/** Returns `true` if this version equals `other` (same flavor, upstream, and downstream). */
equals(other: ExtendedVersion): boolean;
/** Returns `true` if this version is strictly less than `other`. Returns `false` if flavors differ. */
lessThan(other: ExtendedVersion): boolean;
/** Returns `true` if this version is less than or equal to `other`. Returns `false` if flavors differ. */
lessThanOrEqual(other: ExtendedVersion): boolean;
/**
* Parses an extended version string into an `ExtendedVersion`.
*
* @param extendedVersion - A string like `"1.2.3:0"` or `"#bitcoin:1.0.0:0"`
* @throws If the string is not a valid extended version
*/
static parse(extendedVersion: string): ExtendedVersion;
/**
* Parses a legacy "emver" format extended version string.
*
* @param extendedVersion - A version string in the legacy emver format
* @throws If the string is not a valid emver version (error message includes the input string)
*/
static parseEmver(extendedVersion: string): ExtendedVersion;
/**
* Returns an ExtendedVersion with the Upstream major version version incremented by 1
* and sets subsequent digits to zero.
* If no non-zero upstream digit can be found the last upstream digit will be incremented.
*/
incrementMajor(): ExtendedVersion;
/**
* Returns an ExtendedVersion with the Upstream minor version version incremented by 1
* also sets subsequent digits to zero.
* If no non-zero upstream digit can be found the last digit will be incremented.
*/
incrementMinor(): ExtendedVersion;
/**
* Returns a boolean indicating whether a given version satisfies the VersionRange
* !( >= 1:1 <= 2:2) || <=#bitcoin:1.2.0-alpha:0
*/
satisfies(versionRange: VersionRange): boolean;
}
/**
* Compile-time type-checking helper that validates an extended version string literal.
* If the string is invalid, TypeScript will report a type error at the call site.
*
* @example
* ```ts
* testTypeExVer("1.2.3:0") // compiles
* testTypeExVer("#bitcoin:1.0:0") // compiles
* testTypeExVer("invalid") // type error
* ```
*/
export declare const testTypeExVer: <T extends string>(t: T & ValidateExVer<T>) => T & ValidateExVer<T>;
/**
* Compile-time type-checking helper that validates a version string literal.
* If the string is invalid, TypeScript will report a type error at the call site.
*
* @example
* ```ts
* testTypeVersion("1.2.3") // compiles
* testTypeVersion("-3") // type error
* ```
*/
export declare const testTypeVersion: <T extends string>(t: T & ValidateVersion<T>) => T & ValidateVersion<T>;
export {};
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+11
View File
@@ -0,0 +1,11 @@
export { S9pk } from './s9pk';
export { VersionRange, ExtendedVersion, Version } from './exver';
export * as inputSpec from './actions/input';
export * as ISB from './actions/input/builder';
export * as IST from './actions/input/inputSpecTypes';
export * as types from './types';
export * as T from './types';
export * as yaml from 'yaml';
export * as inits from './inits';
export { z } from './zExport';
export * as utils from './util';
+53
View File
@@ -0,0 +1,53 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.utils = exports.z = exports.inits = exports.yaml = exports.T = exports.types = exports.IST = exports.ISB = exports.inputSpec = exports.Version = exports.ExtendedVersion = exports.VersionRange = exports.S9pk = void 0;
var s9pk_1 = require("./s9pk");
Object.defineProperty(exports, "S9pk", { enumerable: true, get: function () { return s9pk_1.S9pk; } });
var exver_1 = require("./exver");
Object.defineProperty(exports, "VersionRange", { enumerable: true, get: function () { return exver_1.VersionRange; } });
Object.defineProperty(exports, "ExtendedVersion", { enumerable: true, get: function () { return exver_1.ExtendedVersion; } });
Object.defineProperty(exports, "Version", { enumerable: true, get: function () { return exver_1.Version; } });
exports.inputSpec = __importStar(require("./actions/input"));
exports.ISB = __importStar(require("./actions/input/builder"));
exports.IST = __importStar(require("./actions/input/inputSpecTypes"));
exports.types = __importStar(require("./types"));
exports.T = __importStar(require("./types"));
exports.yaml = __importStar(require("yaml"));
exports.inits = __importStar(require("./inits"));
var zExport_1 = require("./zExport");
Object.defineProperty(exports, "z", { enumerable: true, get: function () { return zExport_1.z; } });
exports.utils = __importStar(require("./util"));
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../base/lib/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,+BAA6B;AAApB,4FAAA,IAAI,OAAA;AACb,iCAAgE;AAAvD,qGAAA,YAAY,OAAA;AAAE,wGAAA,eAAe,OAAA;AAAE,gGAAA,OAAO,OAAA;AAE/C,6DAA4C;AAC5C,+DAA8C;AAC9C,sEAAqD;AACrD,iDAAgC;AAChC,6CAA4B;AAC5B,6CAA4B;AAC5B,iDAAgC;AAChC,qCAA6B;AAApB,4FAAA,CAAC,OAAA;AAEV,gDAA+B"}
+2
View File
@@ -0,0 +1,2 @@
export * from './setupInit';
export * from './setupUninit';
+19
View File
@@ -0,0 +1,19 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
__exportStar(require("./setupInit"), exports);
__exportStar(require("./setupUninit"), exports);
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../base/lib/inits/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,8CAA2B;AAC3B,gDAA6B"}
+26
View File
@@ -0,0 +1,26 @@
import * as T from '../../../base/lib/types';
/**
* The reason a service's init function is being called:
* - `'install'` — first-time installation
* - `'update'` — after a package update
* - `'restore'` — after restoring from backup
* - `null` — regular startup (no special lifecycle event)
*/
export type InitKind = 'install' | 'update' | 'restore' | null;
/** Function signature for an init handler that runs during service startup. */
export type InitFn<Kind extends InitKind = InitKind> = (effects: T.Effects, kind: Kind) => Promise<void | null | undefined>;
/** Object form of an init handler — implements an `init()` method. */
export interface InitScript<Kind extends InitKind = InitKind> {
init(effects: T.Effects, kind: Kind): Promise<void>;
}
/** Either an {@link InitScript} object or an {@link InitFn} function. */
export type InitScriptOrFn<Kind extends InitKind = InitKind> = InitScript<Kind> | InitFn<Kind>;
/**
* Composes multiple init handlers into a single `ExpectedExports.init`-compatible function.
* Handlers are executed sequentially in the order provided.
*
* @param inits - One or more init handlers to compose
*/
export declare function setupInit(...inits: InitScriptOrFn[]): T.ExpectedExports.init;
/** Normalizes an {@link InitScriptOrFn} into an {@link InitScript} object. */
export declare function setupOnInit(onInit: InitScriptOrFn): InitScript;
+47
View File
@@ -0,0 +1,47 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.setupInit = setupInit;
exports.setupOnInit = setupOnInit;
const util_1 = require("../util");
/**
* Composes multiple init handlers into a single `ExpectedExports.init`-compatible function.
* Handlers are executed sequentially in the order provided.
*
* @param inits - One or more init handlers to compose
*/
function setupInit(...inits) {
return async (opts) => {
for (const idx in inits) {
const init = inits[idx];
const fn = async () => {
let res = () => { };
const complete = new Promise((resolve) => {
res = resolve;
});
const e = opts.effects.child(`init_${idx}`);
e.constRetry = (0, util_1.once)(() => complete.then(() => fn()).catch(console.error));
try {
if ('init' in init)
await init.init(e, opts.kind);
else
await init(e, opts.kind);
}
finally {
res();
}
};
await fn();
}
};
}
/** Normalizes an {@link InitScriptOrFn} into an {@link InitScript} object. */
function setupOnInit(onInit) {
return 'init' in onInit
? onInit
: {
init: async (effects, kind) => {
await onInit(effects, kind);
},
};
}
//# sourceMappingURL=setupInit.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"setupInit.js","sourceRoot":"","sources":["../../../../base/lib/inits/setupInit.ts"],"names":[],"mappings":";;AAmCA,8BAuBC;AAGD,kCAQC;AAnED,kCAA8B;AA2B9B;;;;;GAKG;AACH,SAAgB,SAAS,CAAC,GAAG,KAAuB;IAClD,OAAO,KAAK,EAAE,IAAI,EAAE,EAAE;QACpB,KAAK,MAAM,GAAG,IAAI,KAAK,EAAE,CAAC;YACxB,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,CAAA;YACvB,MAAM,EAAE,GAAG,KAAK,IAAI,EAAE;gBACpB,IAAI,GAAG,GAAgC,GAAG,EAAE,GAAE,CAAC,CAAA;gBAC/C,MAAM,QAAQ,GAAG,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;oBACvC,GAAG,GAAG,OAAO,CAAA;gBACf,CAAC,CAAC,CAAA;gBACF,MAAM,CAAC,GAAc,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,GAAG,EAAE,CAAC,CAAA;gBACtD,CAAC,CAAC,UAAU,GAAG,IAAA,WAAI,EAAC,GAAG,EAAE,CACvB,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAC/C,CAAA;gBACD,IAAI,CAAC;oBACH,IAAI,MAAM,IAAI,IAAI;wBAAE,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;;wBAC5C,MAAM,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;gBAC/B,CAAC;wBAAS,CAAC;oBACT,GAAG,EAAE,CAAA;gBACP,CAAC;YACH,CAAC,CAAA;YACD,MAAM,EAAE,EAAE,CAAA;QACZ,CAAC;IACH,CAAC,CAAA;AACH,CAAC;AAED,8EAA8E;AAC9E,SAAgB,WAAW,CAAC,MAAsB;IAChD,OAAO,MAAM,IAAI,MAAM;QACrB,CAAC,CAAC,MAAM;QACR,CAAC,CAAC;YACE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;gBAC5B,MAAM,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;YAC7B,CAAC;SACF,CAAA;AACP,CAAC"}
+37
View File
@@ -0,0 +1,37 @@
import { ExtendedVersion, VersionRange } from '../../../base/lib/exver';
import * as T from '../../../base/lib/types';
/**
* Function signature for an uninit handler that runs during service shutdown/uninstall.
*/
export type UninitFn = (effects: T.Effects,
/**
* @description the target version to prepare for
*
* on update: the canMigrateFrom of the new package
* on uninstall: null
* on shutdown: the current version
*/
target: VersionRange | ExtendedVersion | null) => Promise<void | null | undefined>;
/** Object form of an uninit handler — implements an `uninit()` method. */
export interface UninitScript {
uninit(effects: T.Effects,
/**
* @description the target version to prepare for
*
* on update: the canMigrateFrom of the new package
* on uninstall: null
* on shutdown: the current version
*/
target: VersionRange | ExtendedVersion | null): Promise<void>;
}
/** Either a {@link UninitScript} object or a {@link UninitFn} function. */
export type UninitScriptOrFn = UninitScript | UninitFn;
/**
* Composes multiple uninit handlers into a single `ExpectedExports.uninit`-compatible function.
* Handlers are executed sequentially in the order provided.
*
* @param uninits - One or more uninit handlers to compose
*/
export declare function setupUninit(...uninits: UninitScriptOrFn[]): T.ExpectedExports.uninit;
/** Normalizes a {@link UninitScriptOrFn} into a {@link UninitScript} object. */
export declare function setupOnUninit(onUninit: UninitScriptOrFn): UninitScript;
+31
View File
@@ -0,0 +1,31 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.setupUninit = setupUninit;
exports.setupOnUninit = setupOnUninit;
/**
* Composes multiple uninit handlers into a single `ExpectedExports.uninit`-compatible function.
* Handlers are executed sequentially in the order provided.
*
* @param uninits - One or more uninit handlers to compose
*/
function setupUninit(...uninits) {
return async (opts) => {
for (const uninit of uninits) {
if ('uninit' in uninit)
await uninit.uninit(opts.effects, opts.target);
else
await uninit(opts.effects, opts.target);
}
};
}
/** Normalizes a {@link UninitScriptOrFn} into a {@link UninitScript} object. */
function setupOnUninit(onUninit) {
return 'uninit' in onUninit
? onUninit
: {
uninit: async (effects, target) => {
await onUninit(effects, target);
},
};
}
//# sourceMappingURL=setupUninit.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"setupUninit.js","sourceRoot":"","sources":["../../../../base/lib/inits/setupUninit.ts"],"names":[],"mappings":";;AA0CA,kCASC;AAGD,sCAQC;AA1BD;;;;;GAKG;AACH,SAAgB,WAAW,CACzB,GAAG,OAA2B;IAE9B,OAAO,KAAK,EAAE,IAAI,EAAE,EAAE;QACpB,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,IAAI,QAAQ,IAAI,MAAM;gBAAE,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;;gBACjE,MAAM,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;QAC9C,CAAC;IACH,CAAC,CAAA;AACH,CAAC;AAED,gFAAgF;AAChF,SAAgB,aAAa,CAAC,QAA0B;IACtD,OAAO,QAAQ,IAAI,QAAQ;QACzB,CAAC,CAAC,QAAQ;QACV,CAAC,CAAC;YACE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE;gBAChC,MAAM,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;YACjC,CAAC;SACF,CAAA;AACP,CAAC"}
@@ -0,0 +1,5 @@
declare const AddressProof: unique symbol;
export type AddressReceipt = {
[AddressProof]: never;
};
export {};
@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=AddressReceipt.js.map
@@ -0,0 +1 @@
{"version":3,"file":"AddressReceipt.js","sourceRoot":"","sources":["../../../../base/lib/interfaces/AddressReceipt.ts"],"names":[],"mappings":""}
+101
View File
@@ -0,0 +1,101 @@
import { Effects } from '../Effects';
import { Origin } from './Origin';
import { AddSslOptions } from '../osBindings';
import { Security } from '../osBindings';
import { BindOptions } from '../osBindings';
import { AlpnInfo } from '../osBindings';
export { AddSslOptions, Security, BindOptions };
export declare const knownProtocols: {
readonly http: {
readonly secure: null;
readonly defaultPort: 80;
readonly withSsl: "https";
readonly alpn: AlpnInfo;
readonly addXForwardedHeaders: true;
};
readonly https: {
readonly secure: {
readonly ssl: true;
};
readonly defaultPort: 443;
readonly addXForwardedHeaders: true;
};
readonly ws: {
readonly secure: null;
readonly defaultPort: 80;
readonly withSsl: "wss";
readonly alpn: AlpnInfo;
readonly addXForwardedHeaders: true;
};
readonly wss: {
readonly secure: {
readonly ssl: true;
};
readonly defaultPort: 443;
readonly addXForwardedHeaders: true;
};
readonly ssh: {
readonly secure: {
readonly ssl: false;
};
readonly defaultPort: 22;
readonly addXForwardedHeaders: false;
};
readonly dns: {
readonly secure: {
readonly ssl: false;
};
readonly defaultPort: 53;
readonly addXForwardedHeaders: false;
};
};
export type Scheme = string | null;
type KnownProtocols = typeof knownProtocols;
type ProtocolsWithSslVariants = {
[K in keyof KnownProtocols]: KnownProtocols[K] extends {
withSsl: string;
} ? K : never;
}[keyof KnownProtocols];
type NotProtocolsWithSslVariants = Exclude<keyof KnownProtocols, ProtocolsWithSslVariants>;
type BindOptionsByKnownProtocol = {
protocol: ProtocolsWithSslVariants;
preferredExternalPort?: number;
addSsl?: Partial<AddSslOptions>;
} | {
protocol: NotProtocolsWithSslVariants;
preferredExternalPort?: number;
addSsl?: AddSslOptions;
};
export type BindOptionsByProtocol = BindOptionsByKnownProtocol | (BindOptions & {
protocol: null;
});
export declare class MultiHost {
readonly options: {
effects: Effects;
id: string;
};
constructor(options: {
effects: Effects;
id: string;
});
/**
* @description Use this function to bind the host to an internal port and configured options for protocol, security, and external port.
*
* @param internalPort - The internal port to be bound.
* @param options - The protocol options for this binding.
* @returns A multi-origin that is capable of exporting one or more service interfaces.
* @example
* In this example, we bind a previously created multi-host to port 80, then select the http protocol and request an external port of 8332.
*
* ```
const uiMultiOrigin = await uiMulti.bindPort(80, {
protocol: 'http',
preferredExternalPort: 8332,
})
* ```
*/
bindPort(internalPort: number, options: BindOptionsByProtocol): Promise<Origin>;
private bindPortForUnknown;
private bindPortForKnown;
private getSslProto;
}
+128
View File
@@ -0,0 +1,128 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MultiHost = exports.knownProtocols = void 0;
const zod_1 = require("zod");
const Origin_1 = require("./Origin");
exports.knownProtocols = {
http: {
secure: null,
defaultPort: 80,
withSsl: 'https',
alpn: { specified: ['http/1.1'] },
addXForwardedHeaders: true,
},
https: {
secure: { ssl: true },
defaultPort: 443,
addXForwardedHeaders: true,
},
ws: {
secure: null,
defaultPort: 80,
withSsl: 'wss',
alpn: { specified: ['http/1.1'] },
addXForwardedHeaders: true,
},
wss: {
secure: { ssl: true },
defaultPort: 443,
addXForwardedHeaders: true,
},
ssh: {
secure: { ssl: false },
defaultPort: 22,
addXForwardedHeaders: false,
},
dns: {
secure: { ssl: false },
defaultPort: 53,
addXForwardedHeaders: false,
},
};
const hasStringProtocol = (v) => zod_1.z.object({ protocol: zod_1.z.string() }).safeParse(v).success;
class MultiHost {
constructor(options) {
this.options = options;
}
/**
* @description Use this function to bind the host to an internal port and configured options for protocol, security, and external port.
*
* @param internalPort - The internal port to be bound.
* @param options - The protocol options for this binding.
* @returns A multi-origin that is capable of exporting one or more service interfaces.
* @example
* In this example, we bind a previously created multi-host to port 80, then select the http protocol and request an external port of 8332.
*
* ```
const uiMultiOrigin = await uiMulti.bindPort(80, {
protocol: 'http',
preferredExternalPort: 8332,
})
* ```
*/
async bindPort(internalPort, options) {
if (hasStringProtocol(options)) {
return await this.bindPortForKnown(options, internalPort);
}
else {
return await this.bindPortForUnknown(internalPort, options);
}
}
async bindPortForUnknown(internalPort, options) {
const binderOptions = {
id: this.options.id,
internalPort,
...options,
};
await this.options.effects.bind(binderOptions);
return new Origin_1.Origin(this, internalPort, null, null);
}
async bindPortForKnown(options, internalPort) {
const protoInfo = exports.knownProtocols[options.protocol];
const preferredExternalPort = options.preferredExternalPort ||
exports.knownProtocols[options.protocol].defaultPort;
const sslProto = this.getSslProto(options);
const addSsl = sslProto
? {
addXForwardedHeaders: exports.knownProtocols[sslProto].addXForwardedHeaders,
preferredExternalPort: exports.knownProtocols[sslProto].defaultPort,
scheme: sslProto,
alpn: 'alpn' in protoInfo ? protoInfo.alpn : null,
...('addSsl' in options ? options.addSsl : null),
}
: options.addSsl
? {
addXForwardedHeaders: false,
preferredExternalPort: 443,
scheme: sslProto,
alpn: null,
...options.addSsl,
}
: null;
const secure = protoInfo.secure ?? null;
await this.options.effects.bind({
id: this.options.id,
internalPort,
preferredExternalPort,
addSsl,
secure,
});
return new Origin_1.Origin(this, internalPort, options.protocol, sslProto);
}
getSslProto(options) {
const proto = options.protocol;
const protoInfo = exports.knownProtocols[proto];
if (inObject('noAddSsl', options) && options.noAddSsl)
return null;
if ('withSsl' in protoInfo && protoInfo.withSsl)
return protoInfo.withSsl;
if (protoInfo.secure?.ssl)
return proto;
return null;
}
}
exports.MultiHost = MultiHost;
function inObject(key, obj) {
return key in obj;
}
//# sourceMappingURL=Host.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"Host.js","sourceRoot":"","sources":["../../../../base/lib/interfaces/Host.ts"],"names":[],"mappings":"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"}
+31
View File
@@ -0,0 +1,31 @@
import { AddressInfo } from '../types';
import { AddressReceipt } from './AddressReceipt';
import { MultiHost, Scheme } from './Host';
import { ServiceInterfaceBuilder } from './ServiceInterfaceBuilder';
export declare class Origin {
readonly host: MultiHost;
readonly internalPort: number;
readonly scheme: string | null;
readonly sslScheme: string | null;
constructor(host: MultiHost, internalPort: number, scheme: string | null, sslScheme: string | null);
build({ username, path, query: search, schemeOverride, }: BuildOptions): AddressInfo;
/**
* @description A function to register a group of origins (<PROTOCOL> :// <HOSTNAME> : <PORT>) with StartOS
*
* The returned addressReceipt serves as proof that the addresses were registered
*
* @param addressInfo
* @returns
*/
export(serviceInterfaces: ServiceInterfaceBuilder[]): Promise<AddressInfo[] & AddressReceipt>;
}
type BuildOptions = {
schemeOverride: {
ssl: Scheme;
noSsl: Scheme;
} | null;
username: string | null;
path: string;
query: Record<string, string>;
};
export {};
+57
View File
@@ -0,0 +1,57 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Origin = void 0;
class Origin {
constructor(host, internalPort, scheme, sslScheme) {
this.host = host;
this.internalPort = internalPort;
this.scheme = scheme;
this.sslScheme = sslScheme;
}
build({ username, path, query: search, schemeOverride, }) {
const qpEntries = Object.entries(search)
.map(([key, val]) => `${encodeURIComponent(key)}=${encodeURIComponent(val)}`)
.join('&');
const qp = qpEntries.length ? `?${qpEntries}` : '';
return {
hostId: this.host.options.id,
internalPort: this.internalPort,
scheme: schemeOverride ? schemeOverride.noSsl : this.scheme,
sslScheme: schemeOverride ? schemeOverride.ssl : this.sslScheme,
suffix: `${path}${qp}`,
username,
};
}
/**
* @description A function to register a group of origins (<PROTOCOL> :// <HOSTNAME> : <PORT>) with StartOS
*
* The returned addressReceipt serves as proof that the addresses were registered
*
* @param addressInfo
* @returns
*/
async export(serviceInterfaces) {
const addressesInfo = [];
for (let serviceInterface of serviceInterfaces) {
const { name, description, id, type, username, path, query: search, schemeOverride, masked, } = serviceInterface.options;
const addressInfo = this.build({
username,
path,
query: search,
schemeOverride,
});
await serviceInterface.options.effects.exportServiceInterface({
id,
name,
description,
addressInfo,
type,
masked,
});
addressesInfo.push(addressInfo);
}
return addressesInfo;
}
}
exports.Origin = Origin;
//# sourceMappingURL=Origin.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"Origin.js","sourceRoot":"","sources":["../../../../base/lib/interfaces/Origin.ts"],"names":[],"mappings":";;;AAKA,MAAa,MAAM;IACjB,YACW,IAAe,EACf,YAAoB,EACpB,MAAqB,EACrB,SAAwB;QAHxB,SAAI,GAAJ,IAAI,CAAW;QACf,iBAAY,GAAZ,YAAY,CAAQ;QACpB,WAAM,GAAN,MAAM,CAAe;QACrB,cAAS,GAAT,SAAS,CAAe;IAChC,CAAC;IAEJ,KAAK,CAAC,EACJ,QAAQ,EACR,IAAI,EACJ,KAAK,EAAE,MAAM,EACb,cAAc,GACD;QACb,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;aACrC,GAAG,CACF,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,GAAG,kBAAkB,CAAC,GAAG,CAAC,IAAI,kBAAkB,CAAC,GAAG,CAAC,EAAE,CACxE;aACA,IAAI,CAAC,GAAG,CAAC,CAAA;QAEZ,MAAM,EAAE,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;QAElD,OAAO;YACL,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE;YAC5B,YAAY,EAAE,IAAI,CAAC,YAAY;YAC/B,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM;YAC3D,SAAS,EAAE,cAAc,CAAC,CAAC,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS;YAC/D,MAAM,EAAE,GAAG,IAAI,GAAG,EAAE,EAAE;YACtB,QAAQ;SACT,CAAA;IACH,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,MAAM,CACV,iBAA4C;QAE5C,MAAM,aAAa,GAAG,EAAE,CAAA;QACxB,KAAK,IAAI,gBAAgB,IAAI,iBAAiB,EAAE,CAAC;YAC/C,MAAM,EACJ,IAAI,EACJ,WAAW,EACX,EAAE,EACF,IAAI,EACJ,QAAQ,EACR,IAAI,EACJ,KAAK,EAAE,MAAM,EACb,cAAc,EACd,MAAM,GACP,GAAG,gBAAgB,CAAC,OAAO,CAAA;YAE5B,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC;gBAC7B,QAAQ;gBACR,IAAI;gBACJ,KAAK,EAAE,MAAM;gBACb,cAAc;aACf,CAAC,CAAA;YAEF,MAAM,gBAAgB,CAAC,OAAO,CAAC,OAAO,CAAC,sBAAsB,CAAC;gBAC5D,EAAE;gBACF,IAAI;gBACJ,WAAW;gBACX,WAAW;gBACX,IAAI;gBACJ,MAAM;aACP,CAAC,CAAA;YAEF,aAAa,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;QACjC,CAAC;QAED,OAAO,aAA+C,CAAA;IACxD,CAAC;CACF;AA9ED,wBA8EC"}
@@ -0,0 +1,46 @@
import { ServiceInterfaceType } from '../types';
import { Effects } from '../Effects';
import { Scheme } from './Host';
/**
* A helper class for creating a Network Interface
*
* Network Interfaces are collections of web addresses that expose the same API or other resource,
* display to the user with under a common name and description.
*
* All URIs on an interface inherit the same ui: bool, basic auth credentials, path, and search (query) params
*
* @param options
* @returns
*/
export declare class ServiceInterfaceBuilder {
readonly options: {
effects: Effects;
name: string;
id: string;
description: string;
type: ServiceInterfaceType;
username: string | null;
path: string;
query: Record<string, string>;
schemeOverride: {
ssl: Scheme;
noSsl: Scheme;
} | null;
masked: boolean;
};
constructor(options: {
effects: Effects;
name: string;
id: string;
description: string;
type: ServiceInterfaceType;
username: string | null;
path: string;
query: Record<string, string>;
schemeOverride: {
ssl: Scheme;
noSsl: Scheme;
} | null;
masked: boolean;
});
}
@@ -0,0 +1,21 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ServiceInterfaceBuilder = void 0;
/**
* A helper class for creating a Network Interface
*
* Network Interfaces are collections of web addresses that expose the same API or other resource,
* display to the user with under a common name and description.
*
* All URIs on an interface inherit the same ui: bool, basic auth credentials, path, and search (query) params
*
* @param options
* @returns
*/
class ServiceInterfaceBuilder {
constructor(options) {
this.options = options;
}
}
exports.ServiceInterfaceBuilder = ServiceInterfaceBuilder;
//# sourceMappingURL=ServiceInterfaceBuilder.js.map
@@ -0,0 +1 @@
{"version":3,"file":"ServiceInterfaceBuilder.js","sourceRoot":"","sources":["../../../../base/lib/interfaces/ServiceInterfaceBuilder.ts"],"names":[],"mappings":";;;AAIA;;;;;;;;;;GAUG;AACH,MAAa,uBAAuB;IAClC,YACW,OAWR;QAXQ,YAAO,GAAP,OAAO,CAWf;IACA,CAAC;CACL;AAfD,0DAeC"}
@@ -0,0 +1,7 @@
import { Effects } from '../types';
export type SetExportedUrls = (opts: {
effects: Effects;
}) => Promise<void>;
export type UpdateExportedUrls = (effects: Effects) => Promise<null>;
export type SetupExportedUrls = (fn: SetExportedUrls) => UpdateExportedUrls;
export declare const setupExportedUrls: SetupExportedUrls;
@@ -0,0 +1,27 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.setupExportedUrls = void 0;
const setupExportedUrls = (fn) => {
return (async (effects) => {
const urls = [];
await fn({
effects: {
...effects,
plugin: {
...effects.plugin,
url: {
...effects.plugin.url,
exportUrl: (params) => {
urls.push(params.hostnameInfo);
return effects.plugin.url.exportUrl(params);
},
},
},
},
});
await effects.plugin.url.clearUrls({ except: urls });
return null;
});
};
exports.setupExportedUrls = setupExportedUrls;
//# sourceMappingURL=setupExportedUrls.js.map
@@ -0,0 +1 @@
{"version":3,"file":"setupExportedUrls.js","sourceRoot":"","sources":["../../../../base/lib/interfaces/setupExportedUrls.ts"],"names":[],"mappings":";;;AAMO,MAAM,iBAAiB,GAAsB,CAAC,EAAmB,EAAE,EAAE;IAC1E,OAAO,CAAC,KAAK,EAAE,OAAgB,EAAE,EAAE;QACjC,MAAM,IAAI,GAAyB,EAAE,CAAA;QACrC,MAAM,EAAE,CAAC;YACP,OAAO,EAAE;gBACP,GAAG,OAAO;gBACV,MAAM,EAAE;oBACN,GAAG,OAAO,CAAC,MAAM;oBACjB,GAAG,EAAE;wBACH,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG;wBACrB,SAAS,EAAE,CAAC,MAAM,EAAE,EAAE;4BACpB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,CAAA;4BAC9B,OAAO,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;wBAC7C,CAAC;qBACF;iBACF;aACF;SACF,CAAC,CAAA;QACF,MAAM,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAA;QACpD,OAAO,IAAI,CAAA;IACb,CAAC,CAAuB,CAAA;AAC1B,CAAC,CAAA;AArBY,QAAA,iBAAiB,qBAqB7B"}
@@ -0,0 +1,15 @@
import * as T from '../types';
import { AddressReceipt } from './AddressReceipt';
declare const UpdateServiceInterfacesProof: unique symbol;
export type UpdateServiceInterfacesReceipt = {
[UpdateServiceInterfacesProof]: never;
};
export type ServiceInterfacesReceipt = Array<T.AddressInfo[] & AddressReceipt>;
export type SetServiceInterfaces<Output extends ServiceInterfacesReceipt> = (opts: {
effects: T.Effects;
}) => Promise<Output>;
export type UpdateServiceInterfaces = (effects: T.Effects) => Promise<null>;
export type SetupServiceInterfaces = <Output extends ServiceInterfacesReceipt>(fn: SetServiceInterfaces<Output>) => UpdateServiceInterfaces;
export declare const NO_INTERFACE_CHANGES: UpdateServiceInterfacesReceipt;
export declare const setupServiceInterfaces: SetupServiceInterfaces;
export {};
@@ -0,0 +1,28 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.setupServiceInterfaces = exports.NO_INTERFACE_CHANGES = void 0;
exports.NO_INTERFACE_CHANGES = {};
const setupServiceInterfaces = (fn) => {
return (async (effects) => {
const bindings = [];
const interfaces = [];
await fn({
effects: {
...effects,
bind: (params) => {
bindings.push({ id: params.id, internalPort: params.internalPort });
return effects.bind(params);
},
exportServiceInterface: (params) => {
interfaces.push(params.id);
return effects.exportServiceInterface(params);
},
},
});
await effects.clearBindings({ except: bindings });
await effects.clearServiceInterfaces({ except: interfaces });
return null;
});
};
exports.setupServiceInterfaces = setupServiceInterfaces;
//# sourceMappingURL=setupInterfaces.js.map
@@ -0,0 +1 @@
{"version":3,"file":"setupInterfaces.js","sourceRoot":"","sources":["../../../../base/lib/interfaces/setupInterfaces.ts"],"names":[],"mappings":";;;AAgBa,QAAA,oBAAoB,GAAG,EAAoC,CAAA;AACjE,MAAM,sBAAsB,GAA2B,CAG5D,EAAgC,EAChC,EAAE;IACF,OAAO,CAAC,KAAK,EAAE,OAAkB,EAAE,EAAE;QACnC,MAAM,QAAQ,GAAe,EAAE,CAAA;QAC/B,MAAM,UAAU,GAA2B,EAAE,CAAA;QAC7C,MAAM,EAAE,CAAC;YACP,OAAO,EAAE;gBACP,GAAG,OAAO;gBACV,IAAI,EAAE,CAAC,MAAoB,EAAE,EAAE;oBAC7B,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,YAAY,EAAE,MAAM,CAAC,YAAY,EAAE,CAAC,CAAA;oBACnE,OAAO,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;gBAC7B,CAAC;gBACD,sBAAsB,EAAE,CAAC,MAAsC,EAAE,EAAE;oBACjE,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;oBAC1B,OAAO,OAAO,CAAC,sBAAsB,CAAC,MAAM,CAAC,CAAA;gBAC/C,CAAC;aACF;SACF,CAAC,CAAA;QACF,MAAM,OAAO,CAAC,aAAa,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAA;QACjD,MAAM,OAAO,CAAC,sBAAsB,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAA;QAC5D,OAAO,IAAI,CAAA;IACb,CAAC,CAA4B,CAAA;AAC/B,CAAC,CAAA;AAzBY,QAAA,sBAAsB,0BAyBlC"}
@@ -0,0 +1,8 @@
import type { AnyVerifyingKey } from './AnyVerifyingKey';
export type AcceptSigners = {
signer: AnyVerifyingKey;
} | {
any: Array<AcceptSigners>;
} | {
all: Array<AcceptSigners>;
};
@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=AcceptSigners.js.map
@@ -0,0 +1 @@
{"version":3,"file":"AcceptSigners.js","sourceRoot":"","sources":["../../../../base/lib/osBindings/AcceptSigners.ts"],"names":[],"mappings":""}
@@ -0,0 +1 @@
export type AcmeProvider = string;
@@ -0,0 +1,4 @@
"use strict";
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=AcmeProvider.js.map
@@ -0,0 +1 @@
{"version":3,"file":"AcmeProvider.js","sourceRoot":"","sources":["../../../../base/lib/osBindings/AcmeProvider.ts"],"names":[],"mappings":";AAAA,4GAA4G"}
@@ -0,0 +1,3 @@
export type AcmeSettings = {
contact: Array<string>;
};
@@ -0,0 +1,4 @@
"use strict";
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=AcmeSettings.js.map
@@ -0,0 +1 @@
{"version":3,"file":"AcmeSettings.js","sourceRoot":"","sources":["../../../../base/lib/osBindings/AcmeSettings.ts"],"names":[],"mappings":";AAAA,4GAA4G"}
+1
View File
@@ -0,0 +1 @@
export type ActionId = string;
+4
View File
@@ -0,0 +1,4 @@
"use strict";
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=ActionId.js.map
@@ -0,0 +1 @@
{"version":3,"file":"ActionId.js","sourceRoot":"","sources":["../../../../base/lib/osBindings/ActionId.ts"],"names":[],"mappings":";AAAA,4GAA4G"}
@@ -0,0 +1,6 @@
import type { Guid } from './Guid';
export type ActionInput = {
eventId: Guid;
spec: Record<string, unknown>;
value: Record<string, unknown> | null;
};
@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=ActionInput.js.map
@@ -0,0 +1 @@
{"version":3,"file":"ActionInput.js","sourceRoot":"","sources":["../../../../base/lib/osBindings/ActionInput.ts"],"names":[],"mappings":""}
@@ -0,0 +1,35 @@
import type { ActionVisibility } from './ActionVisibility';
import type { AllowedStatuses } from './AllowedStatuses';
export type ActionMetadata = {
/**
* A human-readable name
*/
name: string;
/**
* A detailed description of what the action will do
*/
description: string;
/**
* Presents as an alert prior to executing the action. Should be used sparingly but important if the action could have harmful, unintended consequences
*/
warning: string | null;
/**
* One of: "enabled", "hidden", or { disabled: "" }
* - "enabled" - the action is available be run
* - "hidden" - the action cannot be seen or run
* - { disabled: "example explanation" } means the action is visible but cannot be run. Replace "example explanation" with a reason why the action is disable to prevent user confusion.
*/
visibility: ActionVisibility;
/**
* One of: "only-stopped", "only-running", "all"
* - "only-stopped" - the action can only be run when the service is stopped
* - "only-running" - the action can only be run when the service is running
* - "any" - the action can only be run regardless of the service's status
*/
allowedStatuses: AllowedStatuses;
hasInput: boolean;
/**
* If provided, this action will be nested under a header of this value, along with other actions of the same group
*/
group: string | null;
};

Some files were not shown because too many files have changed in this diff Show More