Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Runtime strict-type enforcement for JavaScript objects using Proxies and decorat

| File | Purpose |
|------|---------|
| `src/index.ts` | Core: `BaseClass`, `BaseConstructorPrototype` (default export), `@Strict` decorator, proxy handlers, CJS export setup. |
| `src/index.ts` | Core: `BaseClass`, `BaseConstructorPrototype` (default export), `@Strict` decorator, proxy handlers, construction-field tracking + `finalize` family + `unwrap`, CJS export setup. |
| `src/esm.ts` | ESM entry point. Re-exports default + named bindings from `src/index.ts`. |
| `src/fields.ts` | `FieldConstructor` class for custom property descriptors. |
| `src/errors.ts` | Error message constants. |
Expand Down
33 changes: 30 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,34 @@ user.age = 25;
user.age = '25'; // TypeError: Type Mismatch
```

> **Important:** initialized class fields (`name = 'default'`) are stored as own properties and bypass the proxy. Use `declare name: string;` plus constructor assignment, or see `FieldConstructor` for custom descriptors.
> **Important:** initialized class fields (`name = 'default'`) are stored as own properties and bypass the proxy. Use `declare name: string;` plus constructor assignment, or see `FieldConstructor` for custom descriptors — or call `finalize(instance)` to re-establish hiddenly-added fields as guarded ones (see below).

## Construction Tracking & Finalization

TypeØmatica records every field that passes through its define machinery,
per instance. Fields added with define semantics (class field initializers,
`Object.defineProperty`) bypass the proxy — `finalize` re-establishes them
as guarded fields after the fact:

- `getConstructedFields(instance)` — `Set` of field names that certainly
passed through the machinery (a copy; mutating it affects nothing).
- `finalize(instance)` — auto mode: every hiddenly-added own field is
deleted and re-established through the define machinery; sets the
finalized flag.
- `finalizeBy(instance, fields)` — partial mode: same for a listed subset;
does **not** set the finalized flag.
- `isFinalized(instance)` — `true` only if auto `finalize` ran; manual or
partial finalization leaves it `false`.
- `unwrap(instance, field)` — turns a guarded field back into a plain value
property: boxed primitives are read back via `.valueOf()`, objects and
nullish values are placed as-is. Allowed **only** for fields re-established
by `finalize`/`finalizeBy` — those stay `configurable: true` by design.
Fields guarded since construction are non-configurable forever; that lock
is the essential design of the library, not an accident.

Designed as the `postCreation`-hook companion for mnemonica chains: at each
construction level, diff the instance's own descriptors against
`getConstructedFields(instance)` to find what bypassed the proxy.

---

Expand Down Expand Up @@ -72,8 +99,8 @@ npm run lint:src # ESLint on src/
- `src/esm.ts` — ESM entry point that re-exports the default and named bindings from `src/index.ts`.
- `src/fields.ts` — `FieldConstructor` class for custom property descriptors.
- `src/types/*.ts` — type-category handlers (primitives, objects, functions, special, nullish).
- `test/index.ts` — Jest CJS test suite (50 tests, 100% coverage).
- `test/esm/` — Vitest tests that exercise the actual ESM `exports` map.
- `test/index.ts` — Jest CJS test suite (70 tests, 100% coverage).
- `test/esm/` — Vitest tests that exercise the actual ESM `exports` map, including the native class-fields (define semantics) suite.
- `examples/` — runnable integration examples.

---
Expand Down
1 change: 1 addition & 0 deletions lib/errors.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export declare const ErrorsNames: {
ACCESS_DENIED: string;
RIP_FUNCTIONS: string;
FORBIDDEN_RE: string;
FORBIDDEN_UNWRAP: string;
};
15 changes: 8 additions & 7 deletions lib/errors.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions lib/esm/errors.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export declare const ErrorsNames: {
ACCESS_DENIED: string;
RIP_FUNCTIONS: string;
FORBIDDEN_RE: string;
FORBIDDEN_UNWRAP: string;
};
13 changes: 7 additions & 6 deletions lib/esm/errors.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 18 additions & 18 deletions lib/esm/fields.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 28 additions & 0 deletions lib/esm/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,31 @@ export declare const SymbolInitialValue: symbol;
declare const FieldConstructorExport: typeof FieldConstructor;
export { FieldConstructorExport as FieldConstructor };
export declare const Strict: (_target?: object, options?: TypeomaticaOptions) => <T>(cstr: T) => T;
/**
* Fields that certainly passed through the define machinery for this
* instance. Returns a copy of the internal Set — safe for the caller
* to mutate.
*/
export declare const getConstructedFields: (instance: object) => Set<string | symbol>;
/**
* Auto finalization: every hiddenly-added own field of the instance
* (class fields and other define-semantics writes that bypassed the
* proxy) is deleted and re-established through the define machinery.
* Sets the finalized flag — `true` means auto mode ran.
*/
export declare const finalize: (instance: object) => void;
/**
* Partial finalization: re-establish only the listed fields.
* Does NOT set the finalized flag — that flag means auto mode ran,
* everything else is the user's choice.
*/
export declare const finalizeBy: (instance: object, fields: (string | symbol)[]) => void;
export declare const isFinalized: (instance: object) => boolean;
/**
* Turn a guarded field back into a plain value property.
* Allowed only for fields re-established by finalize/finalizeBy —
* they stay configurable by design. Fields guarded since construction
* are non-configurable: that lock is the essential design of the lib.
* Primitives are read back via .valueOf(); objects are placed as-is.
*/
export declare const unwrap: (instance: object, field: string | symbol) => void;
Loading
Loading