type-fns 1.21.3 → 1.21.5

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.
@@ -0,0 +1,52 @@
1
+ import type { FrozenDeep } from '../types/FrozenDeep';
2
+ /**
3
+ * .what = a companion to the `FrozenDeep` type; freezes a value deep, in place, and returns it
4
+ * typed as `FrozenDeep<T>`
5
+ * .why = the type announces "readonly all the way down"; this enforces it at runtime, so a write
6
+ * the type refuses also throws, and the two cannot drift apart
7
+ *
8
+ * .note = in place, deliberately. it returns the same reference (`asFrozenDeep(x) === x`), since a
9
+ * copy would break identity for every holder of the original, and would leave that
10
+ * original writable
11
+ * .note = all or none. the whole graph is checked before any of it is frozen, so a refusal leaves
12
+ * the value untouched
13
+ * .note = idempotent. a second call on a value it froze returns the same reference, no throw — even
14
+ * from a second loaded copy of this package, via a `Symbol.for` mark on each value
15
+ * .note = a built-in whose mutators write an internal slot — `Map`, `Set`, `WeakMap`, `WeakSet`,
16
+ * `Date`, `DataView`, `RegExp` (`compile`), `ArrayBuffer` (`resize`, `transfer`) — has each
17
+ * mutator refused at runtime with a `ConstraintError` that names the fix, which a bare
18
+ * `Object.freeze` does not. its reads are untouched
19
+ * .note = a write onto a frozen object, array, or function prop throws the engine's own
20
+ * `TypeError` in strict mode (every es module) and is ignored in sloppy mode, per
21
+ * `Object.freeze`. to change a value, copy it and change the copy:
22
+ * `{ ...frozen, page: { ...frozen.page, limit: 50 } }`, `[...frozen.tags, 'push']`
23
+ * .note = a primitive, branded or plain, is returned as-is
24
+ * .note = it reaches non-enumerable own props too, so `new Error('x', { cause })` freezes `cause`.
25
+ * it never enters a function's `prototype`, which every instance of a class shares
26
+ * .note = it throws a `ConstraintError` on what it can not freeze: a typed array with elements, a
27
+ * global or sticky `RegExp` (its reads write `lastIndex`), a slot-mutator built-in already
28
+ * sealed by another freeze, and one with a mutator pinned as an own non-configurable prop.
29
+ * each names the fix, and the `path` in the value where the refused object sits
30
+ * (`value.page.bytes`, `value.byKey.get('sms')`)
31
+ * .note = it freezes every holder's view of a shared sub-object, since it freezes in place: an
32
+ * object the caller shares with another holder is frozen for that holder too
33
+ * .bound = what it does not stop:
34
+ * - the bytes of an `ArrayBuffer`, or of a `DataView`'s buffer, stay writable through any view
35
+ * built on that buffer; a freeze locks the object, never its memory
36
+ * - a class instance's `#private` fields stay writable through its own methods; a private field
37
+ * is no property, so `Object.freeze` can not reach it
38
+ * - a value a getter returns is not frozen, though the type calls it frozen deep; the walk never
39
+ * invokes an accessor, so it runs no caller code
40
+ * - a refused mutator still runs when reached through its prototype, as in
41
+ * `Map.prototype.set.call(frozen, k, v)` or `Reflect.apply`; the shadow is an own prop, and a
42
+ * `Proxy` that would close it breaks identity and `instanceof` (F12)
43
+ *
44
+ * example:
45
+ * ```ts
46
+ * const event = asFrozenDeep({ page: { limit: 10 }, tags: ['sms'] });
47
+ * event.page.limit = 50; // ⛔ tsc refuses; at runtime, a TypeError
48
+ * event.tags.push('push'); // ⛔ tsc refuses; at runtime, a TypeError
49
+ * const next = { ...event, page: { ...event.page, limit: 50 } }; // ✅ a copy is writable
50
+ * ```
51
+ */
52
+ export declare const asFrozenDeep: <T>(value: T) => FrozenDeep<T>;
@@ -0,0 +1,344 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.asFrozenDeep = void 0;
4
+ const helpful_errors_1 = require("helpful-errors");
5
+ const isPresent_1 = require("../checks/isPresent");
6
+ const kindsWithSlotMutators_1 = require("./kindsWithSlotMutators");
7
+ /**
8
+ * .what = the mark `asFrozenDeep` sets on each object whose mutators it refused
9
+ * .why = a second call must skip what a first call froze, and tell it apart from an object sealed
10
+ * by any other freeze, whose mutators can no longer be shadowed
11
+ * .note = `Symbol.for`, so two loaded copies of this package read one mark; a marked object stays
12
+ * idempotent across duplicate installs
13
+ * .note = set as a non-enumerable own prop, so `Object.keys`, json, and the walk skip it
14
+ */
15
+ const mutatorsRefusedMark = Symbol.for('type-fns.asFrozenDeep.mutatorsRefused');
16
+ /**
17
+ * .what = the slot-mutator kind a value is an instance of; else null
18
+ * .why = one lookup feeds the check pass and the freeze pass alike
19
+ */
20
+ const getOneKindWithSlotMutators = (input) => kindsWithSlotMutators_1.kindsWithSlotMutators.find((kind) => input.value instanceof kind.of) ?? null;
21
+ /**
22
+ * .what = the mutators of a value that `asFrozenDeep` has yet to refuse; else none
23
+ * .why = a value already marked owes none, and a value of no slot-mutator kind owes none
24
+ */
25
+ const getAllMutatorsUnrefused = (input) => {
26
+ // a value this package already refused owes none
27
+ if (Object.hasOwn(input.value, mutatorsRefusedMark))
28
+ return [];
29
+ // a slot-mutator kind owes its mutators; any other object owes none
30
+ return getOneKindWithSlotMutators({ value: input.value })?.mutators ?? [];
31
+ };
32
+ /**
33
+ * .what = true for the one own property the walk must never enter: a function's `prototype`
34
+ * .why = a class's `prototype` holds the methods every instance shares; to freeze it would freeze
35
+ * every instance's methods, far past the value the caller passed
36
+ */
37
+ const isPrototypeOfFunction = (input) => typeof input.of === 'function' && input.key === 'prototype';
38
+ /**
39
+ * .what = the key and value of every own data property, symbol keys and non-enumerable keys included
40
+ * .why = `FrozenDeep` maps every key deep, so the walk must reach every value the type claims is
41
+ * frozen — `Object.values` skips symbol keys, and enumerability hides `err.cause`
42
+ * .note = data properties only: an accessor is never invoked, so the walk runs no caller code
43
+ * .note = a function's `prototype` stays out of the walk (see `isPrototypeOfFunction`)
44
+ */
45
+ const getAllOwnDataEntries = (input) => Reflect.ownKeys(input.of)
46
+ .filter((key) => !isPrototypeOfFunction({ of: input.of, key }))
47
+ .map((key) => ({
48
+ key,
49
+ descriptor: Reflect.getOwnPropertyDescriptor(input.of, key),
50
+ }))
51
+ .filter((own) => (0, isPresent_1.isPresent)(own.descriptor) && 'value' in own.descriptor)
52
+ .map((own) => ({ key: own.key, value: own.descriptor?.value }));
53
+ /**
54
+ * .what = a string as a single-quoted js literal
55
+ * .why = a path sits inside the json metadata of a refusal; a double-quoted key would print there
56
+ * as `\"sms\"`, where a single-quoted one prints as `'sms'`
57
+ */
58
+ const asQuotedLiteral = (input) => `'${input.of.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
59
+ /**
60
+ * .what = the path to an own property, from its holder's path, in js accessor notation
61
+ * .why = a refusal deep in a payload must say where it sits, or the caller hunts for it
62
+ * .note = `value.page`, `value.tags[0]`, `value['a-b']`, `value[Symbol(tag)]`
63
+ */
64
+ const asPathToOwnProperty = (input) => {
65
+ const { holder, key } = input;
66
+ if (typeof key === 'symbol')
67
+ return `${holder.path}[${String(key)}]`;
68
+ if (Array.isArray(holder.value) && /^\d+$/.test(key))
69
+ return `${holder.path}[${key}]`;
70
+ if (/^[A-Za-z_$][\w$]*$/.test(key))
71
+ return `${holder.path}.${key}`;
72
+ return `${holder.path}[${asQuotedLiteral({ of: key })}]`;
73
+ };
74
+ /**
75
+ * .what = the path to a map value, from the map's path, in js accessor notation
76
+ * .note = `.get('sms')` for a string, number, or boolean key; else the entry's position,
77
+ * `[...value.byKey.values()][0]`, since an object key has no literal to name it
78
+ */
79
+ const asPathToMapValue = (input) => {
80
+ const { map, key, position } = input;
81
+ if (typeof key === 'string')
82
+ return `${map.path}.get(${asQuotedLiteral({ of: key })})`;
83
+ if (typeof key === 'number' || typeof key === 'boolean')
84
+ return `${map.path}.get(${String(key)})`;
85
+ return `[...${map.path}.values()][${position}]`;
86
+ };
87
+ /**
88
+ * .what = every object reachable from a value, each once, in visit order, with the path that
89
+ * first reached it
90
+ * .why = a pure read of the graph, so every refusal is found before any freeze happens, and each
91
+ * refusal can name where in the value it sits
92
+ * .note = walks own data values (non-enumerable ones too), map keys and values, and set members;
93
+ * never a function's `prototype`; a typed array's elements
94
+ * are numbers, so its interior is not walked. a weak collection can not be enumerated,
95
+ * so its entries are not walked either
96
+ * .note = a shared sub-object is named by the first path that reached it
97
+ * .note = a general graph walk, private while this is its one caller; lift it to `src/` when a
98
+ * second caller (a deep equal, a deep clone) appears
99
+ */
100
+ const getAllReachableObjects = (input) => {
101
+ const reached = new Map();
102
+ /**
103
+ * .what = records one value at its path, then descends into what it holds
104
+ * .why = one recursion for every shape; the `reached` check ends a cycle and dedupes a shared
105
+ * sub-object
106
+ */
107
+ const visit = (step) => {
108
+ // skip a primitive, and a value already reached by another path
109
+ const { value, path } = step;
110
+ if (value === null)
111
+ return;
112
+ if (typeof value !== 'object' && typeof value !== 'function')
113
+ return;
114
+ if (reached.has(value))
115
+ return;
116
+ reached.set(value, path);
117
+ // descend into entries, members, and own data values
118
+ if (ArrayBuffer.isView(value))
119
+ return;
120
+ if (value instanceof Map)
121
+ [...value].forEach(([key, entry], position) => {
122
+ visit({ value: key, path: `[...${path}.keys()][${position}]` });
123
+ visit({
124
+ value: entry,
125
+ path: asPathToMapValue({ map: { path }, key, position }),
126
+ });
127
+ });
128
+ if (value instanceof Set)
129
+ [...value].forEach((member, position) => visit({ value: member, path: `[...${path}][${position}]` }));
130
+ for (const own of getAllOwnDataEntries({ of: value }))
131
+ visit({
132
+ value: own.value,
133
+ path: asPathToOwnProperty({ holder: { path, value }, key: own.key }),
134
+ });
135
+ };
136
+ visit({ value: input.from, path: 'value' });
137
+ return [...reached].map(([object, path]) => ({ object, path }));
138
+ };
139
+ /**
140
+ * .what = refuses a typed array with elements; else null
141
+ * .why = `Object.freeze` throws on a non-empty typed array, since its index slots can not be sealed
142
+ * .note = a `DataView` has no index slots, so it freezes fine; its setters are refused instead
143
+ */
144
+ const getOneRefusalOfTypedArray = (input) => {
145
+ const { value, path } = input;
146
+ if (!ArrayBuffer.isView(value))
147
+ return null;
148
+ if (value instanceof DataView)
149
+ return null;
150
+ if (value.byteLength === 0)
151
+ return null;
152
+ return new helpful_errors_1.ConstraintError('asFrozenDeep can not freeze a typed array with elements', {
153
+ path,
154
+ kind: value.constructor.name,
155
+ byteLength: value.byteLength,
156
+ hint: 'convert it to a plain array first, e.g. Array.from(bytes)',
157
+ });
158
+ };
159
+ /**
160
+ * .what = refuses a global or sticky `RegExp`; else null
161
+ * .why = each `.exec()` / `.test()` on one writes `lastIndex`, so once frozen, a plain read throws
162
+ */
163
+ const getOneRefusalOfRegExpWithLastIndex = (input) => {
164
+ const { value, path } = input;
165
+ if (!(value instanceof RegExp))
166
+ return null;
167
+ if (!value.global && !value.sticky)
168
+ return null;
169
+ return new helpful_errors_1.ConstraintError('asFrozenDeep can not freeze a global or sticky RegExp, since each exec writes its lastIndex', {
170
+ path,
171
+ kind: 'RegExp',
172
+ flags: value.flags,
173
+ hint: "copy it without the g and y flags first: new RegExp(re.source, re.flags.replace(/[gy]/g, ''))",
174
+ });
175
+ };
176
+ /**
177
+ * .what = a class name with its indefinite article, `a Map` or `an ArrayBuffer`
178
+ * .why = a refusal names the kind in prose; `a ArrayBuffer` reads as a typo
179
+ */
180
+ const asKindWithArticle = (input) => `${/^[AEIOU]/i.test(input.kind) ? 'an' : 'a'} ${input.kind}`;
181
+ /**
182
+ * .what = refuses a slot-mutator kind sealed by a freeze other than `asFrozenDeep`; else null
183
+ * .why = a sealed object takes no new own props, so its mutators can no longer be shadowed
184
+ */
185
+ const getOneRefusalOfSealedElsewhere = (input) => {
186
+ const { value, path } = input;
187
+ if (getAllMutatorsUnrefused({ value }).length === 0)
188
+ return null;
189
+ if (Object.isExtensible(value))
190
+ return null;
191
+ const kind = getOneKindWithSlotMutators({ value });
192
+ return new helpful_errors_1.ConstraintError(`asFrozenDeep can not freeze ${asKindWithArticle({ kind: value.constructor.name })} already sealed via Object.freeze, Object.seal, or Object.preventExtensions`, {
193
+ path,
194
+ kind: value.constructor.name,
195
+ hint: `pass it to asFrozenDeep before any other freeze, or copy it first: ${kind?.copy}`,
196
+ });
197
+ };
198
+ /**
199
+ * .what = refuses a slot-mutator kind with a mutator pinned as an own non-configurable prop; else null
200
+ * .why = `Object.defineProperty` can not replace it, so the shadow would throw mid-freeze
201
+ */
202
+ const getOneRefusalOfMutatorPinned = (input) => {
203
+ const { value, path } = input;
204
+ const pinned = getAllMutatorsUnrefused({ value }).find((mutator) => Reflect.getOwnPropertyDescriptor(value, mutator)?.configurable === false);
205
+ if (!pinned)
206
+ return null;
207
+ const kind = getOneKindWithSlotMutators({ value });
208
+ return new helpful_errors_1.ConstraintError(`asFrozenDeep can not freeze ${asKindWithArticle({ kind: value.constructor.name })} whose mutator is pinned as an own non-configurable property`, {
209
+ path,
210
+ kind: value.constructor.name,
211
+ mutator: pinned,
212
+ hint: `copy it first, which carries no own props: ${kind?.copy}`,
213
+ });
214
+ };
215
+ /**
216
+ * .what = every check a single object must clear before `asFrozenDeep` freezes it
217
+ * .why = one list, so a new refusal lands as one new entry
218
+ */
219
+ const freezeRefusalChecks = [
220
+ getOneRefusalOfTypedArray,
221
+ getOneRefusalOfRegExpWithLastIndex,
222
+ getOneRefusalOfSealedElsewhere,
223
+ getOneRefusalOfMutatorPinned,
224
+ ];
225
+ /**
226
+ * .what = the first refusal any object of a graph earns, which names the object's path; else null
227
+ * .why = lets the walk refuse the whole graph before it touches any of it
228
+ */
229
+ const getOneFreezeRefusalOfGraph = (input) => input.reached
230
+ .flatMap((each) => freezeRefusalChecks.map((check) => check({ value: each.object, path: each.path })))
231
+ .find(isPresent_1.isPresent) ?? null;
232
+ /**
233
+ * .what = shadows each named mutator with an own method that throws, then marks the value
234
+ * .why = the only way to refuse `map.set()` or `date.setFullYear()` at runtime and keep the same
235
+ * instance — a `Proxy` or a copy would break identity and `instanceof`
236
+ * .note = the shadows and the mark are non-enumerable, so `Object.keys` and json do not see them
237
+ */
238
+ const setMutatorsRefused = (input) => {
239
+ // shadow each mutator with a method that names the fix
240
+ const kind = input.value.constructor.name;
241
+ const copy = getOneKindWithSlotMutators({ value: input.value })?.copy;
242
+ for (const mutator of input.mutators)
243
+ Object.defineProperty(input.value, mutator, {
244
+ value: () => {
245
+ throw new helpful_errors_1.ConstraintError(`a frozen ${kind} refuses .${mutator}()`, {
246
+ kind,
247
+ mutator,
248
+ hint: `copy it first, then mutate the copy: ${copy}`,
249
+ });
250
+ },
251
+ enumerable: false,
252
+ writable: false,
253
+ configurable: false,
254
+ });
255
+ // mark it, so a later call — from this copy of the package or another — skips it
256
+ Object.defineProperty(input.value, mutatorsRefusedMark, {
257
+ value: true,
258
+ enumerable: false,
259
+ writable: false,
260
+ configurable: false,
261
+ });
262
+ };
263
+ /**
264
+ * .what = freezes one object; a slot-mutator kind also has its mutators refused, once
265
+ * .why = the single place this module writes to a caller's value
266
+ */
267
+ const setFrozen = (input) => {
268
+ // refuse a slot-mutator kind's mutators, unless a prior call already did
269
+ const mutators = getAllMutatorsUnrefused({ value: input.value });
270
+ if (mutators.length > 0)
271
+ setMutatorsRefused({ value: input.value, mutators });
272
+ // freeze the object itself; a no-op if already frozen
273
+ Object.freeze(input.value);
274
+ };
275
+ /**
276
+ * .what = a companion to the `FrozenDeep` type; freezes a value deep, in place, and returns it
277
+ * typed as `FrozenDeep<T>`
278
+ * .why = the type announces "readonly all the way down"; this enforces it at runtime, so a write
279
+ * the type refuses also throws, and the two cannot drift apart
280
+ *
281
+ * .note = in place, deliberately. it returns the same reference (`asFrozenDeep(x) === x`), since a
282
+ * copy would break identity for every holder of the original, and would leave that
283
+ * original writable
284
+ * .note = all or none. the whole graph is checked before any of it is frozen, so a refusal leaves
285
+ * the value untouched
286
+ * .note = idempotent. a second call on a value it froze returns the same reference, no throw — even
287
+ * from a second loaded copy of this package, via a `Symbol.for` mark on each value
288
+ * .note = a built-in whose mutators write an internal slot — `Map`, `Set`, `WeakMap`, `WeakSet`,
289
+ * `Date`, `DataView`, `RegExp` (`compile`), `ArrayBuffer` (`resize`, `transfer`) — has each
290
+ * mutator refused at runtime with a `ConstraintError` that names the fix, which a bare
291
+ * `Object.freeze` does not. its reads are untouched
292
+ * .note = a write onto a frozen object, array, or function prop throws the engine's own
293
+ * `TypeError` in strict mode (every es module) and is ignored in sloppy mode, per
294
+ * `Object.freeze`. to change a value, copy it and change the copy:
295
+ * `{ ...frozen, page: { ...frozen.page, limit: 50 } }`, `[...frozen.tags, 'push']`
296
+ * .note = a primitive, branded or plain, is returned as-is
297
+ * .note = it reaches non-enumerable own props too, so `new Error('x', { cause })` freezes `cause`.
298
+ * it never enters a function's `prototype`, which every instance of a class shares
299
+ * .note = it throws a `ConstraintError` on what it can not freeze: a typed array with elements, a
300
+ * global or sticky `RegExp` (its reads write `lastIndex`), a slot-mutator built-in already
301
+ * sealed by another freeze, and one with a mutator pinned as an own non-configurable prop.
302
+ * each names the fix, and the `path` in the value where the refused object sits
303
+ * (`value.page.bytes`, `value.byKey.get('sms')`)
304
+ * .note = it freezes every holder's view of a shared sub-object, since it freezes in place: an
305
+ * object the caller shares with another holder is frozen for that holder too
306
+ * .bound = what it does not stop:
307
+ * - the bytes of an `ArrayBuffer`, or of a `DataView`'s buffer, stay writable through any view
308
+ * built on that buffer; a freeze locks the object, never its memory
309
+ * - a class instance's `#private` fields stay writable through its own methods; a private field
310
+ * is no property, so `Object.freeze` can not reach it
311
+ * - a value a getter returns is not frozen, though the type calls it frozen deep; the walk never
312
+ * invokes an accessor, so it runs no caller code
313
+ * - a refused mutator still runs when reached through its prototype, as in
314
+ * `Map.prototype.set.call(frozen, k, v)` or `Reflect.apply`; the shadow is an own prop, and a
315
+ * `Proxy` that would close it breaks identity and `instanceof` (F12)
316
+ *
317
+ * example:
318
+ * ```ts
319
+ * const event = asFrozenDeep({ page: { limit: 10 }, tags: ['sms'] });
320
+ * event.page.limit = 50; // ⛔ tsc refuses; at runtime, a TypeError
321
+ * event.tags.push('push'); // ⛔ tsc refuses; at runtime, a TypeError
322
+ * const next = { ...event, page: { ...event.page, limit: 50 } }; // ✅ a copy is writable
323
+ * ```
324
+ */
325
+ const asFrozenDeep = (value) => {
326
+ // find every object the value reaches, with its path
327
+ const reached = getAllReachableObjects({ from: value });
328
+ // refuse the whole graph if any object can not be frozen
329
+ const refusal = getOneFreezeRefusalOfGraph({ reached });
330
+ if (refusal)
331
+ throw refusal;
332
+ // freeze every object
333
+ for (const each of reached)
334
+ setFrozen({ value: each.object });
335
+ /**
336
+ * .why the cast = `FrozenDeep<T>` is a conditional type, so on an unresolved generic typescript
337
+ * defers it and cannot prove `T` satisfies it — a limit of the checker, never a gap in the
338
+ * claim. the walk above is the proof, and it runs first
339
+ * .removal = drops if typescript ever proves a conditional type against its own input
340
+ */
341
+ return value;
342
+ };
343
+ exports.asFrozenDeep = asFrozenDeep;
344
+ //# sourceMappingURL=asFrozenDeep.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asFrozenDeep.js","sourceRoot":"","sources":["../../src/companions/asFrozenDeep.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * .what = the built-ins whose mutators write an internal slot, which `Object.freeze` can not stop
3
+ * .why = `map.set()`, `date.setFullYear()`, `view.setInt8()` each change a frozen object in
4
+ * silence; each owes its mutators shadowed with throwers, and a copy recipe for the hint
5
+ * .note = a file of its own so the doc-drift clamp can read the list, not restate it; the package
6
+ * root does not export it
7
+ */
8
+ export declare const kindsWithSlotMutators: ({
9
+ of: WeakMapConstructor;
10
+ mutators: string[];
11
+ copy: string;
12
+ } | {
13
+ of: WeakSetConstructor;
14
+ mutators: string[];
15
+ copy: string;
16
+ } | {
17
+ of: DateConstructor;
18
+ mutators: string[];
19
+ copy: string;
20
+ } | {
21
+ of: DataViewConstructor;
22
+ mutators: string[];
23
+ copy: string;
24
+ } | {
25
+ of: RegExpConstructor;
26
+ mutators: string[];
27
+ copy: string;
28
+ } | {
29
+ of: ArrayBufferConstructor;
30
+ mutators: string[];
31
+ copy: string;
32
+ })[];
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.kindsWithSlotMutators = void 0;
4
+ /**
5
+ * .what = the names of every `set*` method on a prototype
6
+ * .why = `Date` and `DataView` each hold a closed family of setters; to read them from the
7
+ * prototype keeps the list exact for the runtime at hand (e.g. `setFloat16` where present)
8
+ */
9
+ const getAllSettersOf = (input) => Object.getOwnPropertyNames(input.prototype).filter((name) => name.startsWith('set'));
10
+ /**
11
+ * .what = the built-ins whose mutators write an internal slot, which `Object.freeze` can not stop
12
+ * .why = `map.set()`, `date.setFullYear()`, `view.setInt8()` each change a frozen object in
13
+ * silence; each owes its mutators shadowed with throwers, and a copy recipe for the hint
14
+ * .note = a file of its own so the doc-drift clamp can read the list, not restate it; the package
15
+ * root does not export it
16
+ */
17
+ exports.kindsWithSlotMutators = [
18
+ { of: Map, mutators: ['set', 'delete', 'clear'], copy: 'new Map(frozen)' },
19
+ { of: Set, mutators: ['add', 'delete', 'clear'], copy: 'new Set(frozen)' },
20
+ {
21
+ of: WeakMap,
22
+ mutators: ['set', 'delete'],
23
+ copy: 'a new WeakMap, refilled from the keys you hold',
24
+ },
25
+ {
26
+ of: WeakSet,
27
+ mutators: ['add', 'delete'],
28
+ copy: 'a new WeakSet, refilled from the members you hold',
29
+ },
30
+ {
31
+ of: Date,
32
+ mutators: getAllSettersOf({ prototype: Date.prototype }),
33
+ copy: 'new Date(frozen)',
34
+ },
35
+ {
36
+ of: DataView,
37
+ mutators: getAllSettersOf({ prototype: DataView.prototype }),
38
+ copy: 'new DataView(frozen.buffer.slice(0))',
39
+ },
40
+ {
41
+ of: RegExp,
42
+ mutators: ['compile'],
43
+ copy: 'new RegExp(frozen)',
44
+ },
45
+ {
46
+ of: ArrayBuffer,
47
+ mutators: ['resize', 'transfer', 'transferToFixedLength'].filter((name) => name in ArrayBuffer.prototype),
48
+ copy: 'frozen.slice(0)',
49
+ },
50
+ ];
51
+ //# sourceMappingURL=kindsWithSlotMutators.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kindsWithSlotMutators.js","sourceRoot":"","sources":["../../src/companions/kindsWithSlotMutators.ts"],"names":[],"mappings":";;;AAAA;;;;GAIG;AACH,MAAM,eAAe,GAAG,CAAC,KAA4B,EAAY,EAAE,CACjE,MAAM,CAAC,mBAAmB,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAC1D,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CACvB,CAAC;AAEJ;;;;;;GAMG;AACU,QAAA,qBAAqB,GAAG;IACnC,EAAE,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE;IAC1E,EAAE,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE;IAC1E;QACE,EAAE,EAAE,OAAO;QACX,QAAQ,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC;QAC3B,IAAI,EAAE,gDAAgD;KACvD;IACD;QACE,EAAE,EAAE,OAAO;QACX,QAAQ,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC;QAC3B,IAAI,EAAE,mDAAmD;KAC1D;IACD;QACE,EAAE,EAAE,IAAI;QACR,QAAQ,EAAE,eAAe,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC;QACxD,IAAI,EAAE,kBAAkB;KACzB;IACD;QACE,EAAE,EAAE,QAAQ;QACZ,QAAQ,EAAE,eAAe,CAAC,EAAE,SAAS,EAAE,QAAQ,CAAC,SAAS,EAAE,CAAC;QAC5D,IAAI,EAAE,sCAAsC;KAC7C;IACD;QACE,EAAE,EAAE,MAAM;QACV,QAAQ,EAAE,CAAC,SAAS,CAAC;QACrB,IAAI,EAAE,oBAAoB;KAC3B;IACD;QACE,EAAE,EAAE,WAAW;QACf,QAAQ,EAAE,CAAC,QAAQ,EAAE,UAAU,EAAE,uBAAuB,CAAC,CAAC,MAAM,CAC9D,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,IAAI,WAAW,CAAC,SAAS,CACxC;QACD,IAAI,EAAE,iBAAiB;KACxB;CACF,CAAC"}
package/dist/index.d.ts CHANGED
@@ -9,11 +9,15 @@ export * from './checks/isNotPromise';
9
9
  export * from './checks/isNotUndefined';
10
10
  export * from './checks/isOfEnum';
11
11
  export * from './checks/isPresent';
12
+ export * from './companions/asFrozenDeep';
12
13
  export * from './companions/omit';
13
14
  export * from './companions/pick';
14
15
  export * from './guards/assure';
16
+ export * from './types/ArrayWith';
15
17
  export * from './types/DropFirst';
16
18
  export * from './types/Empty';
19
+ export * from './types/FrozenDeep';
20
+ export * from './types/HasMaybe';
17
21
  export * from './types/Literalize';
18
22
  export * from './types/PickAny';
19
23
  export * from './types/PickOne';
package/dist/index.js CHANGED
@@ -25,11 +25,15 @@ __exportStar(require("./checks/isNotPromise"), exports);
25
25
  __exportStar(require("./checks/isNotUndefined"), exports);
26
26
  __exportStar(require("./checks/isOfEnum"), exports);
27
27
  __exportStar(require("./checks/isPresent"), exports);
28
+ __exportStar(require("./companions/asFrozenDeep"), exports);
28
29
  __exportStar(require("./companions/omit"), exports);
29
30
  __exportStar(require("./companions/pick"), exports);
30
31
  __exportStar(require("./guards/assure"), exports);
32
+ __exportStar(require("./types/ArrayWith"), exports);
31
33
  __exportStar(require("./types/DropFirst"), exports);
32
34
  __exportStar(require("./types/Empty"), exports);
35
+ __exportStar(require("./types/FrozenDeep"), exports);
36
+ __exportStar(require("./types/HasMaybe"), exports);
33
37
  __exportStar(require("./types/Literalize"), exports);
34
38
  __exportStar(require("./types/PickAny"), exports);
35
39
  __exportStar(require("./types/PickOne"), exports);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,iDAA+B;AAC/B,uDAAqC;AACrC,mDAAiC;AACjC,uDAAqC;AACrC,sDAAoC;AACpC,mDAAiC;AACjC,qDAAmC;AACnC,wDAAsC;AACtC,0DAAwC;AACxC,oDAAkC;AAClC,qDAAmC;AACnC,oDAAkC;AAClC,oDAAkC;AAClC,kDAAgC;AAChC,oDAAkC;AAClC,gDAA8B;AAC9B,qDAAmC;AACnC,kDAAgC;AAChC,kDAAgC;AAChC,wDAAsC;AACtC,qDAAmC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,iDAA+B;AAC/B,uDAAqC;AACrC,mDAAiC;AACjC,uDAAqC;AACrC,sDAAoC;AACpC,mDAAiC;AACjC,qDAAmC;AACnC,wDAAsC;AACtC,0DAAwC;AACxC,oDAAkC;AAClC,qDAAmC;AACnC,4DAA0C;AAC1C,oDAAkC;AAClC,oDAAkC;AAClC,kDAAgC;AAChC,oDAAkC;AAClC,oDAAkC;AAClC,gDAA8B;AAC9B,qDAAmC;AACnC,mDAAiC;AACjC,qDAAmC;AACnC,kDAAgC;AAChC,kDAAgC;AAChC,wDAAsC;AACtC,qDAAmC"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * .what = readonly all the way down, with every primitive left exactly as it is
3
+ * .why = typescript's `Readonly<T>` is one level deep, so `frozen.a.b = 1` compiles while a deep
4
+ * runtime freeze throws on it. this type refuses the write at every depth, to match
5
+ * `asFrozenDeep`, which enforces what this type announces
6
+ *
7
+ * .the family = `FrozenDeep<T>` maps any shape to its frozen form:
8
+ * - primitive, branded or plain -> itself. a primitive has no interior to freeze; a branded
9
+ * primitive (`string & { _brand }`) stays assignable back to its brand
10
+ * - function -> its call signature, with its own props readonly
11
+ * - `Map<K, V>` -> `FrozenMap<K, V>`
12
+ * - `Set<T>` -> `FrozenSet<T>`
13
+ * - array or tuple -> a readonly array or readonly tuple; a tuple keeps its arity,
14
+ * labels, and positions
15
+ * - object -> every property readonly, every value frozen deep
16
+ *
17
+ * .note = there is no `FrozenArray`. an array freezes to `readonly T[]` and a tuple to a readonly
18
+ * tuple, so one name could not cover both without the loss of a tuple's shape
19
+ *
20
+ * .bound = what this type does not refuse:
21
+ * - a hand-off to a mutable OBJECT parameter compiles. typescript ignores `readonly` in
22
+ * structural assignability, so `{ readonly a: 1 }` is assignable to `{ a: 1 }`. a mutable
23
+ * ARRAY parameter is refused, since `ReadonlyArray` lacks the mutator methods
24
+ * - a write through `any` compiles. `any` opts out of every check
25
+ * - `Array.isArray(frozen)` narrows to `any[]`, which hands `.push` back. prefer a `typeof` or
26
+ * a shape check on a frozen value
27
+ * - a `Date`, `DataView`, `WeakMap`, `WeakSet`, `RegExp`, or `ArrayBuffer` keeps the object arm,
28
+ * so its mutator methods (`setFullYear`, `setInt8`, `set`, `add`, `compile`, `resize`) still
29
+ * type-check — the object arm guards props, not methods, and a frozen `Date` stays assignable
30
+ * back to `Date`. `asFrozenDeep` refuses each of those calls at runtime with a `ConstraintError`
31
+ * - a class instance's methods still type-check, so a method that writes a `#private` field
32
+ * compiles; no freeze reaches a private field
33
+ * - a getter's value is typed frozen deep, yet `asFrozenDeep` never invokes a getter, so the
34
+ * object it returns stays writable at runtime
35
+ */
36
+ export type FrozenDeep<T> = T extends string | number | boolean | bigint | symbol | null | undefined ? T : T extends (...args: never[]) => unknown ? FrozenFunction<T> : T extends ReadonlyMap<infer TKey, infer TValue> ? FrozenMap<TKey, TValue> : T extends ReadonlySet<infer TItem> ? FrozenSet<TItem> : T extends object ? {
37
+ readonly [TKey in keyof T]: FrozenDeep<T[TKey]>;
38
+ } : T;
39
+ /**
40
+ * .what = a map whose mutators are absent and whose keys and values are frozen deep
41
+ * .why = `ReadonlyMap<K, V>` alone refuses `.set` yet leaves each value writable; this freezes both
42
+ */
43
+ export type FrozenMap<TKey, TValue> = ReadonlyMap<FrozenDeep<TKey>, FrozenDeep<TValue>>;
44
+ /**
45
+ * .what = a set whose mutators are absent and whose members are frozen deep
46
+ * .why = `ReadonlySet<T>` alone refuses `.add` yet leaves each member writable; this freezes both
47
+ */
48
+ export type FrozenSet<TItem> = ReadonlySet<FrozenDeep<TItem>>;
49
+ /**
50
+ * .what = a function with its call signature kept and its own props readonly
51
+ * .why = a function is an object too, and the runtime freeze refuses a write onto its props
52
+ * .note = a function with no own props passes through whole, so its generics and overloads survive.
53
+ * a function WITH own props is rebuilt from `Parameters` / `ReturnType`, since an
54
+ * intersection with `T` would keep each prop writable (a prop is readonly in an
55
+ * intersection only where every member marks it so). the rebuild keeps one call
56
+ * signature, so a generic or overloaded function with own props narrows to its last one
57
+ */
58
+ type FrozenFunction<T extends (...args: never[]) => unknown> = [
59
+ keyof T
60
+ ] extends [never] ? T : ((...args: Parameters<T>) => ReturnType<T>) & {
61
+ readonly [TKey in keyof T]: FrozenDeep<T[TKey]>;
62
+ };
63
+ export {};
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=FrozenDeep.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FrozenDeep.js","sourceRoot":"","sources":["../../src/types/FrozenDeep.ts"],"names":[],"mappings":""}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * makes the listed key(s) `K` of `T` optional — the make-maybe companion of the
3
+ * make-required (`HasMetadata`) / omit (`OmitMetadata`) trio.
4
+ *
5
+ * use when a value's key is authored without it, then bound at a later
6
+ * lifecycle stage (e.g. a domain key like `exid` known only at activation).
7
+ *
8
+ * for example, `HasMaybe<{ role: string; exid: string }, 'exid'>` produces
9
+ * `{ role: string; exid?: string }`.
10
+ *
11
+ * note:
12
+ * - this type preserves all non-`K` keys exactly (required keys stay required)
13
+ * - this type only loosens the listed `K` — it does not add keys and does not
14
+ * touch any key outside `K`
15
+ * - `K extends keyof T` — a key not on `T` is a compile error
16
+ */
17
+ export type HasMaybe<T extends Record<string, any>, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=HasMaybe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HasMaybe.js","sourceRoot":"","sources":["../../src/types/HasMaybe.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "type-fns",
3
3
  "author": "ehmpathy",
4
4
  "description": "A set of types, type checks, and type guards for simpler, safer, and easier to read code.",
5
- "version": "1.21.3",
5
+ "version": "1.21.5",
6
6
  "repository": "ehmpathy/type-fns",
7
7
  "homepage": "https://github.com/ehmpathy/type-fns",
8
8
  "keywords": [
@@ -55,7 +55,7 @@
55
55
  "preversion": "npm run prepush",
56
56
  "postversion": "git push origin HEAD --tags --no-verify",
57
57
  "prepare:husky": "husky install && chmod ug+x .husky/*",
58
- "prepare:rhachet": "rhachet init --keys --hooks --roles mechanic behaver driver reviewer librarian ergonomist architect",
58
+ "prepare:rhachet": "rhachet init --hooks --roles mechanic behaver driver architect ergonomist reviewer dreamer dispatcher",
59
59
  "prepare": "if [ -e .git ] && [ -z $CI ]; then npm run prepare:husky && npm run prepare:rhachet; fi",
60
60
  "test:lint:cycles": "dpdm --no-warning --no-tree --exit-code circular:1 --exclude '^$' 'src/**/*.ts'",
61
61
  "upgrade:rhachet": "rhachet upgrade"
@@ -87,12 +87,13 @@
87
87
  "esbuild-register": "3.6.0",
88
88
  "husky": "8.0.3",
89
89
  "jest": "30.2.0",
90
- "rhachet": "1.39.9",
91
- "rhachet-brains-anthropic": "0.4.0",
90
+ "rhachet": "1.48.0",
91
+ "rhachet-brains-anthropic": "0.4.3",
92
+ "rhachet-brains-fireworksai": "0.2.3",
92
93
  "rhachet-brains-xai": "0.3.3",
93
- "rhachet-roles-bhrain": "0.23.11",
94
- "rhachet-roles-bhuild": "0.17.1",
95
- "rhachet-roles-ehmpathy": "1.34.25",
94
+ "rhachet-roles-bhrain": "0.37.2",
95
+ "rhachet-roles-bhuild": "0.21.36",
96
+ "rhachet-roles-ehmpathy": "1.39.1",
96
97
  "test-fns": "1.15.8",
97
98
  "ts-jest": "29.1.3",
98
99
  "ts-node": "10.9.2",
package/readme.md CHANGED
@@ -101,7 +101,28 @@ const str: string = strStr[0];
101
101
  const num: number = numStrStr[0];
102
102
  ```
103
103
 
104
- Useful, for example, if you want to change the first parameter of a function while keeping the rest the same.
104
+ useful, for example, to change the first parameter of a function while the rest stay the same.
105
+
106
+ ### `FrozenDeep`
107
+
108
+ the generic type `FrozenDeep` is readonly all the way down, where `Readonly` stops at one level. every primitive, branded primitives included, stays exactly as it is.
109
+
110
+ ```ts
111
+ import { FrozenDeep } from 'type-fns';
112
+
113
+ type IsoTimeStamp = string & { _dglo: 'iso-time.IsoTimeStamp' };
114
+ type Event = { at: IsoTimeStamp; page: { limit: number }; tags: string[]; seen: Map<string, number> };
115
+
116
+ declare const event: FrozenDeep<Event>;
117
+ event.page.limit = 50; // 🛑 readonly at depth two
118
+ event.tags.push('push'); // 🛑 a readonly array has no push
119
+ event.seen.set('sms', 1); // 🛑 a FrozenMap has no set
120
+ const at: IsoTimeStamp = event.at; // ✅ a branded primitive stays assignable back
121
+ ```
122
+
123
+ one bound to know: a frozen object still passes into a parameter typed as a mutable object, since typescript does not check `readonly` on object props at assignment. a frozen array does not pass into a mutable array parameter, so an object with an array field inside is refused there too — the cause is the array, never a brand. declare the parameter `FrozenDeep<T>` to accept it.
124
+
125
+ maps and sets freeze to `FrozenMap` / `FrozenSet`, also exported. pair it with `asFrozenDeep` to enforce the same at runtime.
105
126
 
106
127
  ## type guards
107
128
 
@@ -207,3 +228,36 @@ const processUser = (input: { uuid: string }) => {
207
228
  // ...
208
229
  };
209
230
  ```
231
+
232
+ ## companions
233
+
234
+ ### `asFrozenDeep`
235
+
236
+ the `asFrozenDeep` function freezes a value and every value it reaches, in place, and returns it typed as `FrozenDeep<T>`. a write the type refuses also throws at runtime.
237
+
238
+ ```ts
239
+ import { asFrozenDeep } from 'type-fns';
240
+
241
+ const event = asFrozenDeep({ page: { limit: 10 }, seen: new Map([['sms', 1]]) });
242
+ event.page.limit = 50; // 🛑 typescript error; at runtime, a TypeError
243
+ event.seen.set('push', 2); // 🛑 typescript error; at runtime, a ConstraintError (a bare Object.freeze permits it)
244
+ ```
245
+
246
+ it returns the same reference, so any other holder of a sub-object sees it frozen too. it reaches non-enumerable props as well, so `new Error('x', { cause })` freezes `cause`, yet never enters a function's `prototype`, which every instance of a class shares. it is safe on cycles and shared sub-objects, and a second call is a no-op. it checks the whole value before it freezes any of it, so a refusal (a typed array with elements, a global or sticky `RegExp`, or a map sealed by a bare `Object.freeze`) leaves the value untouched. each refusal is a `ConstraintError` that names where the refused object sits and how to fix it:
247
+
248
+ ```
249
+ ✋ ConstraintError: asFrozenDeep can not freeze a typed array with elements
250
+
251
+ {
252
+ "path": "value.event.records[1].raw",
253
+ "kind": "Uint8Array",
254
+ "byteLength": 1,
255
+ "hint": "convert it to a plain array first, e.g. Array.from(bytes)"
256
+ }
257
+ ```
258
+
259
+ a bare `Object.freeze` lets a built-in change its internal state through its own methods. `asFrozenDeep` refuses those at runtime: `Map` and `Set` (`set`, `add`, `delete`, `clear`), `WeakMap` and `WeakSet` (`set`, `add`, `delete`), `Date` and `DataView` (every `set*`), `RegExp` (`compile`), and `ArrayBuffer` (`resize`, `transfer`). the type keeps the object arm for all but `Map` and `Set`, so those calls still compile.
260
+
261
+ what it does not reach: the bytes of an `ArrayBuffer` through a view built on it, a class instance's `#private` fields through its own methods, the value a getter returns (it never invokes a getter), and a refused mutator called through its prototype, as in `Map.prototype.set.call(frozen, k, v)`.
262
+
263
+ to change a frozen value, copy it and change the copy: `{ ...event, page: { ...event.page, limit: 50 } }`, or `new Map(event.seen)` for a map.