@crustjs/store 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -27
- package/dist/index.d.ts +83 -106
- package/dist/index.js +3 -3
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -85,7 +85,7 @@ const config = createStore({
|
|
|
85
85
|
const data = createStore({
|
|
86
86
|
dirPath: dataDir("my-cli"),
|
|
87
87
|
fields: {
|
|
88
|
-
bookmarks: { type: "
|
|
88
|
+
bookmarks: { type: "string", array: true, default: [] },
|
|
89
89
|
},
|
|
90
90
|
});
|
|
91
91
|
|
|
@@ -320,8 +320,9 @@ Add per-field validation to enforce config integrity on every read, write, updat
|
|
|
320
320
|
### Schema-driven validation
|
|
321
321
|
|
|
322
322
|
The easiest way to add validation is with the built-in `field()` factory.
|
|
323
|
-
It builds a
|
|
324
|
-
|
|
323
|
+
It builds a `FieldDef` from any [Standard Schema v1](https://standardschema.dev/)
|
|
324
|
+
object. Crust stores raw values; the schema validates and transforms them on
|
|
325
|
+
read/write.
|
|
325
326
|
|
|
326
327
|
```ts
|
|
327
328
|
import { z } from "zod";
|
|
@@ -358,33 +359,34 @@ const store = createStore({
|
|
|
358
359
|
});
|
|
359
360
|
```
|
|
360
361
|
|
|
362
|
+
Missing persisted values flow through the schema as `undefined`:
|
|
363
|
+
|
|
364
|
+
- `field(z.string().default("x"))` returns `"x"` on read when the field
|
|
365
|
+
is missing from disk.
|
|
366
|
+
- `field(z.string().optional())` returns `undefined`.
|
|
367
|
+
- `field(z.string())` fails read-time validation with a `VALIDATION`
|
|
368
|
+
error when the field is missing.
|
|
369
|
+
|
|
361
370
|
#### Schema-derived defaults and TypeScript
|
|
362
371
|
|
|
363
372
|
Standard Schema v1 has no spec-portable type-level access to schema
|
|
364
|
-
defaults.
|
|
373
|
+
defaults. Crust does not inspect schemas to populate runtime metadata;
|
|
374
|
+
defaults are materialized on read by validating `undefined` through the
|
|
375
|
+
schema. As a result:
|
|
365
376
|
|
|
366
|
-
- `field(z.string().default("x"))`
|
|
367
|
-
|
|
368
|
-
- `field(z.string(), { default: "x" })`
|
|
369
|
-
narrows the inferred config type to `string`.
|
|
377
|
+
- `field(z.string().default("x"))` returns `"x"` on read, but the
|
|
378
|
+
inferred config type is `string | undefined` (NOT narrowed).
|
|
379
|
+
- `field(z.string(), { default: "x" })` keeps `"x"` as the Crust-level
|
|
380
|
+
default AND narrows the inferred config type to `string`.
|
|
370
381
|
|
|
371
382
|
Prefer the explicit form when the field is required to always have a
|
|
372
383
|
value at use sites.
|
|
373
384
|
|
|
374
385
|
#### `field()` throws on invalid input
|
|
375
386
|
|
|
376
|
-
`field()` throws `CrustStoreError("DEFINITION")` when handed a
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
error's `details.vendor` field carries the schema's vendor name (when
|
|
380
|
-
available) to help locate the offending call site.
|
|
381
|
-
|
|
382
|
-
> **Effect ≥ 3.14.2 required.** Effect 3.14.2 made `standardSchemaV1(...)`
|
|
383
|
-
> wrappers expose `.ast`, which is what the validate registry walks. On
|
|
384
|
-
> Effect 3.14.0 / 3.14.1 the wrapper is a plain object and introspection
|
|
385
|
-
> silently fails. See
|
|
386
|
-
> [`@crustjs/validate` Effect setup](https://crustjs.com/docs/modules/validate#quick-start--effect)
|
|
387
|
-
> for the floor and a workaround.
|
|
387
|
+
`field()` throws `CrustStoreError("DEFINITION")` when handed a value that
|
|
388
|
+
is not a Standard Schema v1 object. `type` is optional for schema-backed
|
|
389
|
+
fields; pass it through `opts` only as legacy metadata for tooling.
|
|
388
390
|
|
|
389
391
|
### Custom validators
|
|
390
392
|
|
|
@@ -470,6 +472,7 @@ All errors thrown by `@crustjs/store` are instances of `CrustStoreError` with a
|
|
|
470
472
|
| `PARSE` | Malformed JSON in persisted config file | `{ path: string }` |
|
|
471
473
|
| `IO` | Filesystem read, write, or delete failure | `{ path, operation }` |
|
|
472
474
|
| `VALIDATION` | Config fails validator on read, write, update, or patch | `{ operation, issues }` |
|
|
475
|
+
| `DEFINITION` | `field()` received an invalid (non-Standard-Schema) input | `{ vendor? }` |
|
|
473
476
|
|
|
474
477
|
### Catching errors by code
|
|
475
478
|
|
|
@@ -529,13 +532,13 @@ import type {
|
|
|
529
532
|
StoreUpdater,
|
|
530
533
|
FieldDef,
|
|
531
534
|
FieldsDef,
|
|
535
|
+
FieldOptions,
|
|
532
536
|
InferStoreConfig,
|
|
533
|
-
StoreValidator,
|
|
534
|
-
StoreValidatorResult,
|
|
535
537
|
StoreValidatorIssue,
|
|
536
538
|
StoreErrorCode,
|
|
537
|
-
StoreValidationIssue,
|
|
538
539
|
ValidationErrorDetails,
|
|
540
|
+
DefinitionErrorDetails,
|
|
541
|
+
ValueType,
|
|
539
542
|
PlatformEnv,
|
|
540
543
|
} from "@crustjs/store";
|
|
541
544
|
```
|
|
@@ -547,13 +550,13 @@ import type {
|
|
|
547
550
|
| `StoreUpdater` | Updater function type `(current: T) => T`. |
|
|
548
551
|
| `FieldDef` | Single field definition with `type`, optional `default`, and optional `validate`. |
|
|
549
552
|
| `FieldsDef` | Record of field names to `FieldDef` definitions. |
|
|
553
|
+
| `FieldOptions` | Optional Crust metadata accepted by the `field()` factory (`type`, `default`, `array`, `description`). |
|
|
550
554
|
| `InferStoreConfig` | Inferred store state type from a `FieldsDef` definition. |
|
|
551
|
-
| `StoreValidator` | Validator function contract `(value: unknown) => StoreValidatorResult`. |
|
|
552
|
-
| `StoreValidatorResult` | Discriminated union: `{ ok: true, value: T } \| { ok: false, issues: StoreValidatorIssue[] }`. |
|
|
553
555
|
| `StoreValidatorIssue` | `{ message: string, path: string }`. |
|
|
554
|
-
| `StoreValidationIssue` | Validation issue in error details payload. |
|
|
555
556
|
| `ValidationErrorDetails` | Error details for `VALIDATION` code: `{ operation, issues }`. |
|
|
556
|
-
| `
|
|
557
|
+
| `DefinitionErrorDetails` | Error details for `DEFINITION` code: `{ vendor? }`. |
|
|
558
|
+
| `StoreErrorCode` | Union of error codes: `"PATH" \| "PARSE" \| "IO" \| "VALIDATION" \| "DEFINITION"`. |
|
|
559
|
+
| `ValueType` | Supported field type literals: `"string" \| "number" \| "boolean"`. |
|
|
557
560
|
| `PlatformEnv` | Injectable platform environment for testing path helpers. |
|
|
558
561
|
| `CrustStoreError` | Typed error class with `code`, `details`, and `cause`. |
|
|
559
562
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,6 @@
|
|
|
1
|
+
import { BaseValueType, ResolvePrimitive } from "@crustjs/utils";
|
|
1
2
|
/** Supported type literals for store fields. */
|
|
2
|
-
type ValueType =
|
|
3
|
-
/**
|
|
4
|
-
* Resolves a type literal to its corresponding TypeScript primitive type.
|
|
5
|
-
*
|
|
6
|
-
* - `"string"` → `string`
|
|
7
|
-
* - `"number"` → `number`
|
|
8
|
-
* - `"boolean"` → `boolean`
|
|
9
|
-
*/
|
|
10
|
-
type ResolvePrimitive<T extends ValueType> = T extends "string" ? string : T extends "number" ? number : T extends "boolean" ? boolean : never;
|
|
3
|
+
type ValueType = BaseValueType;
|
|
11
4
|
/** Shared fields present on every store field definition. */
|
|
12
5
|
interface FieldDefBase<V> {
|
|
13
6
|
/** Human-readable description for documentation and tooling. */
|
|
@@ -41,6 +34,8 @@ interface FieldDefBase<V> {
|
|
|
41
34
|
} | Promise<{
|
|
42
35
|
value: unknown;
|
|
43
36
|
}>;
|
|
37
|
+
/** Internal marker: validate missing values as `undefined` for schema-backed fields. */
|
|
38
|
+
validateMissing?: true;
|
|
44
39
|
}
|
|
45
40
|
/** Base for scalar (non-array) fields — `array` must be omitted. */
|
|
46
41
|
interface ScalarFieldBase<V> extends FieldDefBase<V> {
|
|
@@ -110,9 +105,18 @@ interface BooleanArrayFieldDef extends ArrayFieldBase<boolean[]> {
|
|
|
110
105
|
* } satisfies FieldsDef;
|
|
111
106
|
* ```
|
|
112
107
|
*/
|
|
113
|
-
|
|
108
|
+
interface RawScalarFieldDef extends ScalarFieldBase<unknown> {
|
|
109
|
+
type?: never;
|
|
110
|
+
default?: unknown;
|
|
111
|
+
}
|
|
112
|
+
interface RawArrayFieldDef extends ArrayFieldBase<unknown[]> {
|
|
113
|
+
type?: never;
|
|
114
|
+
default?: readonly unknown[];
|
|
115
|
+
}
|
|
116
|
+
type FieldDef = StringFieldDef | NumberFieldDef | BooleanFieldDef | StringArrayFieldDef | NumberArrayFieldDef | BooleanArrayFieldDef | RawScalarFieldDef | RawArrayFieldDef;
|
|
114
117
|
/** Record mapping field names to their definitions. */
|
|
115
118
|
type FieldsDef = Record<string, FieldDef>;
|
|
119
|
+
declare const FIELD_SCHEMA_OUTPUT: unique symbol;
|
|
116
120
|
/**
|
|
117
121
|
* Infer the resolved type for a single field definition.
|
|
118
122
|
*
|
|
@@ -121,12 +125,20 @@ type FieldsDef = Record<string, FieldDef>;
|
|
|
121
125
|
* - **no default** → `primitive | undefined` (optional)
|
|
122
126
|
*/
|
|
123
127
|
type InferFieldValue<F extends FieldDef> = F extends {
|
|
128
|
+
readonly [FIELD_SCHEMA_OUTPUT]?: infer O;
|
|
129
|
+
} ? F extends {
|
|
130
|
+
default: infer D;
|
|
131
|
+
} ? D : O | undefined : F extends {
|
|
132
|
+
type: ValueType;
|
|
133
|
+
} ? F extends {
|
|
124
134
|
array: true;
|
|
125
135
|
} ? F extends {
|
|
126
136
|
default: readonly ResolvePrimitive<F["type"]>[];
|
|
127
137
|
} ? ResolvePrimitive<F["type"]>[] : ResolvePrimitive<F["type"]>[] | undefined : F extends {
|
|
128
138
|
default: ResolvePrimitive<F["type"]>;
|
|
129
|
-
} ? ResolvePrimitive<F["type"]> : ResolvePrimitive<F["type"]> | undefined
|
|
139
|
+
} ? ResolvePrimitive<F["type"]> : ResolvePrimitive<F["type"]> | undefined : F extends {
|
|
140
|
+
default: infer D;
|
|
141
|
+
} ? D : unknown;
|
|
130
142
|
/**
|
|
131
143
|
* Maps a full {@link FieldsDef} record to the inferred config object type.
|
|
132
144
|
*
|
|
@@ -339,11 +351,10 @@ interface ValidationErrorDetails {
|
|
|
339
351
|
*
|
|
340
352
|
* Returned when a `field()` call cannot produce a valid `FieldDef` from the
|
|
341
353
|
* provided schema and options — e.g. the input is not a Standard Schema v1
|
|
342
|
-
* object
|
|
343
|
-
* supplied so the runtime CLI type cannot be inferred.
|
|
354
|
+
* object.
|
|
344
355
|
*/
|
|
345
356
|
interface DefinitionErrorDetails {
|
|
346
|
-
/** The schema vendor name, when
|
|
357
|
+
/** The schema vendor name, when available. */
|
|
347
358
|
vendor?: string;
|
|
348
359
|
}
|
|
349
360
|
/**
|
|
@@ -453,16 +464,14 @@ declare class CrustStoreError<C extends StoreErrorCode = StoreErrorCode> extends
|
|
|
453
464
|
*/
|
|
454
465
|
withCause(cause: unknown): this;
|
|
455
466
|
}
|
|
456
|
-
import {
|
|
467
|
+
import { BaseValueType as BaseValueType2 } from "@crustjs/utils";
|
|
468
|
+
import { InferOutput, StandardSchema } from "@crustjs/utils/schema";
|
|
457
469
|
/**
|
|
458
|
-
* Optional
|
|
459
|
-
* {@link field}.
|
|
470
|
+
* Optional Crust metadata for the `FieldDef` shape produced by {@link field}.
|
|
460
471
|
*
|
|
461
|
-
* Every key is optional.
|
|
462
|
-
*
|
|
463
|
-
*
|
|
464
|
-
* Valibot, ArkType), `type` MUST be supplied explicitly because no
|
|
465
|
-
* inference is available.
|
|
472
|
+
* Every key is optional. Crust does not infer metadata from schemas; schemas
|
|
473
|
+
* validate and transform actual runtime values, including missing values passed
|
|
474
|
+
* as `undefined`.
|
|
466
475
|
*
|
|
467
476
|
* No `validate` key — validation flows exclusively through the schema.
|
|
468
477
|
* If extra checks are needed, refine the schema with `.refine(...)`
|
|
@@ -472,122 +481,79 @@ import { InferOutput, StandardSchema } from "@crustjs/schema-utils";
|
|
|
472
481
|
* `default` key when the user wants a non-`undefined` field.
|
|
473
482
|
*/
|
|
474
483
|
interface FieldOptions<T = unknown> {
|
|
475
|
-
type?:
|
|
484
|
+
type?: BaseValueType2;
|
|
476
485
|
description?: string;
|
|
477
486
|
/**
|
|
478
|
-
*
|
|
479
|
-
* a value for it.
|
|
480
|
-
*
|
|
481
|
-
*
|
|
482
|
-
* NOT narrow the TypeScript type — pass it explicitly here for tight
|
|
483
|
-
* typing.
|
|
487
|
+
* Crust metadata default for this field when the persisted state does not
|
|
488
|
+
* contain a value for it. Schema defaults should usually live in the schema;
|
|
489
|
+
* missing persisted values are validated as `undefined`, so `.default()` works
|
|
490
|
+
* naturally at read time.
|
|
484
491
|
*/
|
|
485
492
|
default?: T;
|
|
486
493
|
/** Mark this field as an array (collects values into an array). */
|
|
487
494
|
array?: true;
|
|
488
495
|
}
|
|
489
|
-
type
|
|
490
|
-
type
|
|
491
|
-
|
|
492
|
-
/**
|
|
493
|
-
* Resolve the runtime CLI value-type from a Standard Schema's output type.
|
|
494
|
-
*
|
|
495
|
-
* Array schemas are detected via `IsArrayOutput`; their element type drives
|
|
496
|
-
* the value-type literal so `field(z.array(z.string()))` resolves to
|
|
497
|
-
* `{ type: "string"; array: true }`.
|
|
498
|
-
*/
|
|
499
|
-
type IsArrayOutput<S> = S extends StandardSchema<infer _I, infer Out> ? StripUndefined<Out> extends readonly unknown[] ? true : false : false;
|
|
500
|
-
type ArrayElementOutput<S> = S extends StandardSchema<infer _I, infer Out> ? StripUndefined<Out> extends readonly (infer E)[] ? E : never : never;
|
|
501
|
-
type ResolveScalarType<S> = S extends StandardSchema<infer _I, infer Out> ? PrimitiveToValueType<StripUndefined<Out>> : ValueType2;
|
|
502
|
-
/** A scalar `FieldDef` with no narrowed default. */
|
|
503
|
-
type ScalarFieldDef<T extends ValueType2> = {
|
|
504
|
-
readonly type: T;
|
|
505
|
-
readonly description?: string;
|
|
506
|
-
readonly validate: (value: unknown) => Promise<{
|
|
507
|
-
value: unknown;
|
|
508
|
-
}>;
|
|
509
|
-
};
|
|
510
|
-
/** A scalar `FieldDef` with a narrowed default. */
|
|
511
|
-
type ScalarFieldDefWithDefault<
|
|
512
|
-
T extends ValueType2,
|
|
513
|
-
D
|
|
514
|
-
> = {
|
|
515
|
-
readonly type: T;
|
|
496
|
+
type RawSchemaFieldDef<Out> = {
|
|
497
|
+
readonly type?: never;
|
|
498
|
+
readonly array?: never;
|
|
516
499
|
readonly description?: string;
|
|
517
|
-
readonly default: D;
|
|
518
500
|
readonly validate: (value: unknown) => Promise<{
|
|
519
501
|
value: unknown;
|
|
520
502
|
}>;
|
|
503
|
+
readonly validateMissing: true;
|
|
504
|
+
readonly [FIELD_SCHEMA_OUTPUT]?: Out;
|
|
521
505
|
};
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
506
|
+
type SchemaFieldDefWithOptions<
|
|
507
|
+
Out,
|
|
508
|
+
Type extends FieldOptions["type"],
|
|
509
|
+
Array extends true | undefined,
|
|
510
|
+
D = never
|
|
511
|
+
> = Omit<RawSchemaFieldDef<Out>, "type" | "array"> & (Type extends NonNullable<FieldOptions["type"]> ? {
|
|
512
|
+
readonly type: Type;
|
|
513
|
+
} : {
|
|
514
|
+
readonly type?: never;
|
|
515
|
+
}) & (Array extends true ? {
|
|
525
516
|
readonly array: true;
|
|
526
|
-
|
|
527
|
-
readonly
|
|
528
|
-
|
|
529
|
-
}>;
|
|
530
|
-
};
|
|
531
|
-
/** An array `FieldDef` with a narrowed default. */
|
|
532
|
-
type ArrayFieldDefWithDefault<
|
|
533
|
-
T extends ValueType2,
|
|
534
|
-
D
|
|
535
|
-
> = {
|
|
536
|
-
readonly type: T;
|
|
537
|
-
readonly array: true;
|
|
538
|
-
readonly description?: string;
|
|
517
|
+
} : {
|
|
518
|
+
readonly array?: never;
|
|
519
|
+
}) & ([D] extends [never] ? unknown : {
|
|
539
520
|
readonly default: D;
|
|
540
|
-
|
|
541
|
-
value: unknown;
|
|
542
|
-
}>;
|
|
543
|
-
};
|
|
521
|
+
});
|
|
544
522
|
/**
|
|
545
523
|
* `FieldDef` inferred from a Standard Schema, with no narrowed default.
|
|
546
524
|
*
|
|
547
|
-
* Schema
|
|
548
|
-
*
|
|
549
|
-
*
|
|
550
|
-
* narrow.
|
|
551
|
-
*/
|
|
552
|
-
type SchemaFieldDef<S extends StandardSchema> = IsArrayOutput<S> extends true ? ArrayFieldDef<PrimitiveToValueType<StripUndefined<ArrayElementOutput<S>>>> : ScalarFieldDef<ResolveScalarType<S>>;
|
|
553
|
-
/**
|
|
554
|
-
* `FieldDef` inferred from a Standard Schema, with the explicit `opts.default`
|
|
555
|
-
* narrowed into the type.
|
|
525
|
+
* Standard Schema v1 has no spec-portable type-level access to defaults, so
|
|
526
|
+
* schema `.default()` does not narrow the inferred TypeScript type. Pass
|
|
527
|
+
* `field(schema, { default: x })` explicitly to narrow.
|
|
556
528
|
*/
|
|
557
|
-
type
|
|
558
|
-
S extends StandardSchema,
|
|
559
|
-
D
|
|
560
|
-
> = IsArrayOutput<S> extends true ? ArrayFieldDefWithDefault<PrimitiveToValueType<StripUndefined<ArrayElementOutput<S>>>, D> : ScalarFieldDefWithDefault<ResolveScalarType<S>, D>;
|
|
529
|
+
type SchemaFieldDef<S extends StandardSchema> = RawSchemaFieldDef<InferOutput<S>>;
|
|
561
530
|
/**
|
|
562
531
|
* Define a `@crustjs/store` field from any Standard Schema v1.
|
|
563
532
|
*
|
|
564
533
|
* Returns a value that satisfies store's `FieldDef` discriminated union.
|
|
565
|
-
*
|
|
566
|
-
*
|
|
567
|
-
*
|
|
568
|
-
* any key explicitly — explicit values win silently.
|
|
534
|
+
* Crust stores raw values and lets the schema validate and transform them on
|
|
535
|
+
* read/write; missing persisted values flow through the schema as `undefined`,
|
|
536
|
+
* so `.optional()` and `.default()` behave naturally.
|
|
569
537
|
*
|
|
570
538
|
* The returned `validate` is an async function that throws an `Error` with
|
|
571
539
|
* the schema's normalized issue messages on failure (matches store's
|
|
572
540
|
* `FieldDef.validate` contract).
|
|
573
541
|
*
|
|
574
|
-
* **Type-level defaults**:
|
|
575
|
-
*
|
|
576
|
-
*
|
|
542
|
+
* **Type-level defaults**: Standard Schema v1 has no spec-portable type-level
|
|
543
|
+
* access to defaults. For tight typing of default-bearing fields, pass
|
|
544
|
+
* `default` via `opts`:
|
|
577
545
|
*
|
|
578
546
|
* ```ts
|
|
579
|
-
* field(z.string().default("x"))
|
|
580
|
-
* field(z.string(), { default: "x" })
|
|
547
|
+
* field(z.string().default("x")) // value materializes on read; state: string | undefined
|
|
548
|
+
* field(z.string(), { default: "x" }) // Crust-level default; state: string
|
|
581
549
|
* ```
|
|
582
550
|
*
|
|
583
551
|
* @param schema - Any Standard Schema v1 object (Zod schemas natively;
|
|
584
552
|
* Effect schemas wrapped via `Schema.standardSchemaV1`;
|
|
585
553
|
* Valibot/ArkType/Sury/etc. as-is)
|
|
586
|
-
* @param opts - Optional
|
|
587
|
-
* the introspected values silently
|
|
554
|
+
* @param opts - Optional Crust-level metadata
|
|
588
555
|
* @throws {CrustStoreError} With code `"DEFINITION"` when the input is not
|
|
589
|
-
* a Standard Schema
|
|
590
|
-
* and `opts.type` was not supplied.
|
|
556
|
+
* a Standard Schema v1 object.
|
|
591
557
|
*
|
|
592
558
|
* @example Zod
|
|
593
559
|
* ```ts
|
|
@@ -620,11 +586,22 @@ type SchemaFieldDefWithDefault<
|
|
|
620
586
|
declare function field<S extends StandardSchema>(schema: S): SchemaFieldDef<S>;
|
|
621
587
|
declare function field<
|
|
622
588
|
S extends StandardSchema,
|
|
623
|
-
D extends InferOutput<S
|
|
589
|
+
D extends InferOutput<S>,
|
|
590
|
+
const Type extends FieldOptions<InferOutput<S>>["type"] = undefined,
|
|
591
|
+
const Array extends true | undefined = undefined
|
|
624
592
|
>(schema: S, opts: FieldOptions<InferOutput<S>> & {
|
|
625
593
|
default: D;
|
|
626
|
-
|
|
627
|
-
|
|
594
|
+
type?: Type;
|
|
595
|
+
array?: Array;
|
|
596
|
+
}): SchemaFieldDefWithOptions<InferOutput<S>, Type, Array, D>;
|
|
597
|
+
declare function field<
|
|
598
|
+
S extends StandardSchema,
|
|
599
|
+
const Type extends FieldOptions<InferOutput<S>>["type"] = undefined,
|
|
600
|
+
const Array extends true | undefined = undefined
|
|
601
|
+
>(schema: S, opts: FieldOptions<InferOutput<S>> & {
|
|
602
|
+
type?: Type;
|
|
603
|
+
array?: Array;
|
|
604
|
+
}): SchemaFieldDefWithOptions<InferOutput<S>, Type, Array>;
|
|
628
605
|
/**
|
|
629
606
|
* Encapsulates runtime environment values needed for path resolution.
|
|
630
607
|
*
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
class
|
|
3
|
-
${
|
|
4
|
-
`)}`;throw new
|
|
2
|
+
class X extends Error{code;details;cause;constructor(q,H,G){super(H);this.name="CrustStoreError",this.code=q,this.details=G}is(q){return this.code===q}withCause(q){return this.cause=q,this}}import{isStandardSchema as m,normalizeStandardIssues as p}from"@crustjs/utils/schema";function d(q){return async(H)=>{let G=await q["~standard"].validate(H);if(G.issues){let Q=p(G.issues).map((Y)=>Y.path?`${Y.path}: ${Y.message}`:Y.message);throw Error(Q.join("; "))}return{value:G.value}}}function n(q,H){if(!m(q))throw new X("DEFINITION",`field(): argument must be a Standard Schema v1 object (got ${typeof q})`,{});let K={validate:d(q),validateMissing:!0};if(H?.type!==void 0)K.type=H.type;if(H?.array===!0)K.array=!0;if(H?.description!==void 0)K.description=H.description;if(H&&"default"in H)K.default=H.default;return K}import{homedir as o}from"os";import{join as Z}from"path";var l="config";function R(){return{platform:process.platform,env:process.env,homedir:o()}}function U(q){if(!q||q.trim().length===0)throw new X("PATH","appName must be a non-empty string",{path:q??""});if(q.includes("/")||q.includes("\\"))throw new X("PATH","appName must not contain path separators",{path:q})}function i(q){if(!q||q.trim().length===0)throw new X("PATH","name must be a non-empty string",{path:q??""});if(q.includes("/")||q.includes("\\"))throw new X("PATH","name must not contain path separators",{path:q});if(q.endsWith(".json"))throw new X("PATH","name must not include the .json extension",{path:q})}function s(q){if(!q||q.trim().length===0)throw new X("PATH","dirPath must be a non-empty string",{path:q??""});if(!(q.startsWith("/")||/^[A-Za-z]:[/\\]/.test(q)))throw new X("PATH","dirPath must be an absolute path",{path:q});if(q.endsWith(".json"))throw new X("PATH","dirPath must be a directory path, not a file path (should not end with .json)",{path:q})}function b(q,H,G,K){let Q=q.env[H],Y=Q&&Q.trim().length>0?Q:Z(q.homedir,...G);return Z(Y,K)}function A(q){throw new X("PATH",`Unsupported platform: ${q}`,{path:q})}function t(q,H){U(q);let G=H??R();switch(G.platform){case"linux":case"darwin":return b(G,"XDG_CONFIG_HOME",[".config"],q);case"win32":{let K=G.env.APPDATA,Q=K&&K.trim().length>0?K:Z(G.homedir,"AppData","Roaming");return Z(Q,q)}default:A(G.platform)}}function r(q,H){U(q);let G=H??R();switch(G.platform){case"linux":case"darwin":return b(G,"XDG_DATA_HOME",[".local","share"],q);case"win32":{let K=G.env.LOCALAPPDATA,Q=K&&K.trim().length>0?K:Z(G.homedir,"AppData","Local");return Z(Q,q,"Data")}default:A(G.platform)}}function a(q,H){U(q);let G=H??R();switch(G.platform){case"linux":case"darwin":return b(G,"XDG_STATE_HOME",[".local","state"],q);case"win32":{let K=G.env.LOCALAPPDATA,Q=K&&K.trim().length>0?K:Z(G.homedir,"AppData","Local");return Z(Q,q,"State")}default:A(G.platform)}}function e(q,H){U(q);let G=H??R();switch(G.platform){case"linux":case"darwin":return b(G,"XDG_CACHE_HOME",[".cache"],q);case"win32":{let K=G.env.LOCALAPPDATA,Q=K&&K.trim().length>0?K:Z(G.homedir,"AppData","Local");return Z(Q,q,"Cache")}default:A(G.platform)}}function k(q,H){s(q);let G=H??l;if(H!==void 0)i(H);return Z(q,`${G}.json`)}import{coerceBooleanString as Mq,tryCoerceNumber as Wq}from"@crustjs/utils";function w(q,H,G=!0){let K={};for(let[Q,Y]of Object.entries(H))if(q&&Q in q){let x=q[Q];K[Q]=Array.isArray(x)?[...x]:x}else if("default"in Y&&Y.default!==void 0)K[Q]=Array.isArray(Y.default)?[...Y.default]:Y.default;if(!G&&q!==void 0){for(let[Q,Y]of Object.entries(q))if(!(Q in H))K[Q]=Array.isArray(Y)?[...Y]:Y}return K}import{randomUUID as qq}from"crypto";import{mkdir as Gq,readFile as Hq,rename as Kq,rm as Qq,unlink as Xq,writeFile as Yq}from"fs/promises";import{dirname as $q,join as Lq}from"path";async function S(q){let H;try{H=await Hq(q,"utf-8")}catch(G){if(N(G))return;throw new X("IO",`Failed to read config file: ${q}`,{path:q,operation:"read"}).withCause(G)}try{return JSON.parse(H)}catch(G){throw new X("PARSE",`Malformed JSON in config file: ${q}`,{path:q}).withCause(G)}}async function C(q,H){let G=$q(q),K=Lq(G,`.config-${qq()}.tmp`);try{await Gq(G,{recursive:!0})}catch(Q){throw new X("IO",`Failed to create config directory: ${G}`,{path:q,operation:"write"}).withCause(Q)}try{let Q=JSON.stringify(H,null,"\t");await Yq(K,Q,"utf-8")}catch(Q){throw await y(K),new X("IO",`Failed to write config file: ${q}`,{path:q,operation:"write"}).withCause(Q)}try{await Kq(K,q)}catch(Q){throw await y(K),new X("IO",`Failed to finalize config file: ${q}`,{path:q,operation:"write"}).withCause(Q)}}async function g(q){try{await Xq(q)}catch(H){if(N(H))return;throw new X("IO",`Failed to delete config file: ${q}`,{path:q,operation:"delete"}).withCause(H)}}function N(q){return q!==null&&typeof q==="object"&&"code"in q&&q.code==="ENOENT"}async function y(q){try{await Qq(q,{force:!0})}catch{}}function E(q){return typeof q==="object"&&q!==null&&"value"in q&&!("ok"in q)}function Oq(q){let{dirPath:H,name:G,fields:K,pruneUnknown:Q}=q,Y=k(H,G),x=Q??!0;function j($,L){if(L===void 0)return $;if(L==="number"&&typeof $==="string")return Wq($)??$;if(L==="boolean"&&typeof $==="string")return Mq($);return $}function T($){let M={...$};for(let[W,O]of Object.entries(K)){if(!(W in M))continue;let _=M[W];if(O.array===!0&&Array.isArray(_)){M[W]=_.map((B)=>j(B,O.type));continue}M[W]=j(_,O.type)}return M}async function V($,L){let M=[],W=$;for(let[O,_]of Object.entries(K)){if(!_.validate)continue;let B=W[O];if(B===void 0&&_.validateMissing!==!0)continue;let F;try{F=await _.validate(B)}catch(I){let J=I instanceof Error?I.message:"Validation failed";M.push({message:J,path:O});continue}if(F===void 0)continue;if(E(F)){let I=F.value;if(L==="read"){if(B===void 0)W[O]=I;continue}if(!Bun.deepEquals(I,B)){let J;try{J=await _.validate(I)}catch(z){let u=z instanceof Error?z.message:"re-validation failed";M.push({message:`read-unstable transform: ${u}`,path:O});continue}if(J!==void 0&&(!E(J)||!Bun.deepEquals(J.value,I))){M.push({message:"read-unstable transform: output would be transformed again on re-read",path:O});continue}W[O]=I}continue}throw TypeError("FieldDef.validate must return void or { value } (see @crustjs/store validation docs).")}if(M.length>0){let O=M.map((B)=>` - ${B.path}: ${B.message}`),_=`Store validation failed (${L})
|
|
3
|
+
${O.join(`
|
|
4
|
+
`)}`;throw new X("VALIDATION",_,{operation:L,issues:M})}}async function D(){let $=await S(Y),L=w($,K,x);return T(L)}async function P(){let $=await D();return await V($,"read"),$}async function c($){let L=T($);await V(L,"write"),await C(Y,L)}async function f($){let L=await D(),M=$(L),W=T(M);await V(W,"update"),await C(Y,W)}async function v($){let M={...await D(),...$},W=T(M);await V(W,"patch"),await C(Y,W)}async function h(){await g(Y)}return{read:P,write:c,update:f,patch:v,reset:h}}export{a as stateDir,n as field,r as dataDir,Oq as createStore,t as configDir,e as cacheDir,X as CrustStoreError};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crustjs/store",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "DX-first, typed persistence for CLI apps with config/data/state/cache separation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -48,13 +48,13 @@
|
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@crustjs/config": "0.0.0",
|
|
50
50
|
"bunup": "^0.16.31",
|
|
51
|
-
"effect": "3.
|
|
51
|
+
"effect": "3.21.2",
|
|
52
52
|
"zod": "^4.0.0"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"typescript": "^6.0.3"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@crustjs/
|
|
58
|
+
"@crustjs/utils": "0.0.2"
|
|
59
59
|
}
|
|
60
60
|
}
|