typed-snapshot
v0.5.0
Published
Generate typed TypeScript snapshots (const/enum/union) to file.
Downloads
207
Maintainers
Readme
typed-snapshot
Generate typed TypeScript snapshots to disk — as a constant, an enum, a union type, an as const object, or an interface — with optional import type headers and timestamps.
All functions return a Result (Done | Fail) from lite-fp instead of throwing: failures are values you match on, with typed error kinds.
Install
npm i typed-snapshotor in a monorepo, add as a local workspace and npm run build inside the package.
Usage
import { Result, writeTypedVariableToFile } from 'typed-snapshot';
const result = await writeTypedVariableToFile({
type: '{ base: Tokens; total: number }',
data: { base: 'USDT', total: 123.4567 },
variableName: 'PORTFOLIO_SALDO',
outputPath: './data/portfolio_saldo.ts',
importPath: '../src/enums/Tokens',
importTypeName: 'Tokens',
includeTimestamp: true,
});
Result.match(result, {
done: () => console.log('snapshot written'),
fail: (error) => console.error(error.kind),
});Arrays → as const object
import { generateAsConstFromArray } from 'typed-snapshot';
const result = generateAsConstFromArray(['BNB', 'BTC', 'USDT', 'ETH'], 'Token');
Result.match(result, {
done: (code) => console.log(code),
fail: (error) => console.error(error.kind),
});Generates:
export const Token = {
BNB: 'BNB',
BTC: 'BTC',
USDT: 'USDT',
ETH: 'ETH',
} as const;Arrays → enum
generateEnumFromArray(['BTC', 'ETH', 'USDT'], 'Token');
// Done: export enum Token { BTC = 'BTC', ETH = 'ETH', USDT = 'USDT' }Arrays → union type
generateTypeFromArray(['BTCUSDT', 'ETHUSDT'], 'Symbol');
// Done: export type Symbol = 'BTCUSDT' | 'ETHUSDT';Arrays → interface
generateInterfaceFromArray(['BNB', 'BTC', 'USDT', 'ETH'], 'TokenData');
// Done: export interface TokenData { BNB: 'BNB'; BTC: 'BTC'; USDT: 'USDT'; ETH: 'ETH'; }Arrays → Maybe-wrapped const (asconst-maybe)
Annotates the constant with lite-fp's Maybe<T> (T | null | undefined) and an
explicit readonly shape — contextual typing keeps literal types, so no
as const assertion is emitted. The generated file imports Maybe from
lite-fp automatically.
generateAsConstMaybeFromArray(['BNB', 'BTC'], 'Token');import type { Maybe } from 'lite-fp';
export const Token: Maybe<{
readonly BNB: 'BNB';
readonly BTC: 'BTC';
}> = {
BNB: 'BNB',
BTC: 'BTC',
};Consumers handle absence explicitly:
import { Maybe } from 'lite-fp';
import { Token } from './data/Token';
const names = Maybe.map(Token, (t) => Object.keys(t)); // Maybe<string[]>Arrays → Maybe-wrapped union (type-maybe)
generateTypeMaybeFromArray(['BTCUSDT', 'ETHUSDT'], 'Symbol');
// Done:
// import type { Maybe } from 'lite-fp';
// export type Symbol = Maybe<'BTCUSDT' | 'ETHUSDT'>;Both -maybe formats require every item to be a string or number — any other
value returns Fail({ $: 'no-valid-items' }).
Arrays → just(...) const (asconst-just)
Wraps the object in lite-fp's runtime just constructor. The as const
assertion keeps literal types, and the generated file imports just from
'lite-fp' automatically (a value import, unlike the -maybe type import).
generateAsConstJustFromArray(['BNB', 'BTC'], 'Token');import { just } from 'lite-fp';
export const Token = just({
BNB: 'BNB',
BTC: 'BTC',
} as const);Composing generation with writing
Because generators are pure and return Result, you can build a pipeline that
only touches the disk when the content is valid:
import { Result } from 'lite-fp';
import { generateAsConstFromArray } from 'typed-snapshot';
import { writeTypedVariableToFile } from 'typed-snapshot';
const header = '// tokens snapshot\n';
const file = Result.flatMap(
generateAsConstFromArray(tokens, 'Token'),
(code) =>
writeTypedVariableToFile({
type: 'never',
data: [code], // or write `code` yourself
variableName: 'Token',
outputPath: './data/Token.ts',
}),
);API
writeTypedVariableToFile(options): Promise<Result<void, SnapshotError>>type: string — type annotation for the exported const (plain mode)data: unknown — data to serialize (must be a non-empty array for non-plain formats)variableName: string — export name (also used as enum/type/interface name)outputPath: string — file path to writeimportPath?,importTypeName?— optionalimport typeheader (emitted only when both are set)includeTimestamp?(default true)typeFormat?:'plain' | 'enum' | 'type' | 'asconst' | 'interface' | 'asconst-maybe' | 'type-maybe' | 'asconst-just'
generateEnumFromArray(data, enumName): Result<string, GenerationError>generateTypeFromArray(data, typeName): Result<string, GenerationError>generateAsConstFromArray(data, variableName): Result<string, GenerationError>generateInterfaceFromArray(data, interfaceName): Result<string, GenerationError>generateAsConstMaybeFromArray(data, variableName): Result<string, GenerationError>generateTypeMaybeFromArray(data, typeName): Result<string, GenerationError>generateAsConstJustFromArray(data, variableName): Result<string, GenerationError>generateContent(options): Result<string, GenerationError>— pure file body builderdescribeSnapshotError(error): string— human-readable message for any errorisValidIdentifier(value)/emitTypedConst(name, type, value)— low-level helpers
Result, done, fail, Done and Fail are re-exported from lite-fp for convenience.
Error tags
Errors are tagged unions — branch on error.$:
| $ | meaning |
| --------------------- | ---------------------------------------------------- |
| not-array | data was not an array (extra field: received) |
| empty-data | data array was empty |
| no-valid-items | nothing usable remained (enum/type) or a non-primitive item was found (-maybe formats) |
| invalid-name | export name is not a TypeScript identifier |
| invalid-output-path | output path missing/blank |
| io | filesystem write failed (original error under cause)|
Notes
- The library has a single runtime dependency:
lite-fp. Build withtsup. - Files generated with
-maybeformats importMaybefrom'lite-fp', andasconst-justimportsjust— keep the package resolvable from the generated files (it ships as a regular dependency, so npm/pnpm/yarn hoisting normally covers it). - Nothing throws: every failure mode above comes back as
Fail. - For
enummode, string values must be valid TypeScript identifiers to become keys; numbers becomeVALUE_<n>keys; anything else is filtered out. - For
asconst,interface, andasconst-maybemodes:- String values that are valid TypeScript identifiers use the value as the property key
- Invalid identifiers use
ITEM_${index}as the property key - Number values use
VALUE_${number}as the property key
Migrating from 0.3 to 0.4
Breaking changes:
- All generators now take
data: unknownand returnResult<string, GenerationError>. UseResult.match/Result.getOrElseto extract code. writeTypedVariableToFilereturnsPromise<Result<void, SnapshotError>>and never throws.- Empty arrays and "no valid items" are now explicit
Fails instead of silently exporting a raw array. variableNamemust be a full TypeScript identifier (previously only spaces were rejected).
- const code = generateAsConstFromArray(tokens, 'Token');
+ const code = Result.getOrElse(generateAsConstFromArray(tokens, 'Token'), '');
- await writeTypedVariableToFile({ ... });
+ const result = await writeTypedVariableToFile({ ... });
+ Result.match(result, { done: () => {}, fail: (e) => console.error(e.$) });Contributing
- Fork the repository.
- Create a new branch:
git checkout -b feature-name. - Commit your changes:
git commit -m 'Add some feature'. - Push to the branch:
git push origin feature-name. - Open a pull request.
License
Distributed under the MIT License. See the LICENSE file for more details.
