diff --git a/docs/en/v1/api/common/index.md b/docs/en/v1/api/common/index.md
index 108b2201..c18448ec 100644
--- a/docs/en/v1/api/common/index.md
+++ b/docs/en/v1/api/common/index.md
@@ -127,6 +127,9 @@ Async pause to wait for a certain time.
### [memo](/en/v1/api/common/memo)
Evaluates a function only once and reuses the result (lazy memoization).
+### [memoObject](/en/v1/api/common/memoObject)
+Memoizes an object exposed through a `Proxy` and keeps keys aligned with writes.
+
### [memoPromise](/en/v1/api/common/memoPromise)
Lazy memoization for functions returning a value or a promise.
diff --git a/docs/en/v1/api/common/memo.md b/docs/en/v1/api/common/memo.md
index 5a746427..150237a8 100644
--- a/docs/en/v1/api/common/memo.md
+++ b/docs/en/v1/api/common/memo.md
@@ -5,8 +5,8 @@ prev:
text: "sleep"
link: "/en/v1/api/common/sleep"
next:
- text: "memoPromise"
- link: "/en/v1/api/common/memoPromise"
+ text: "memoObject"
+ link: "/en/v1/api/common/memoObject"
---
# memo
diff --git a/docs/en/v1/api/common/memoObject.md b/docs/en/v1/api/common/memoObject.md
new file mode 100644
index 00000000..0944407e
--- /dev/null
+++ b/docs/en/v1/api/common/memoObject.md
@@ -0,0 +1,49 @@
+---
+outline: [2, 3]
+description: "The memoObject() function builds a proxy around a memoized object. The getter is evaluated once, then reads/writes target the same reference."
+prev:
+ text: "memo"
+ link: "/en/v1/api/common/memo"
+next:
+ text: "memoPromise"
+ link: "/en/v1/api/common/memoPromise"
+---
+
+# memoObject
+
+The **`memoObject()`** function builds a proxy around a memoized object. The getter is evaluated lazily on first access, then all reads/writes target the same object.
+
+## Interactive example
+
+
+
+## Syntax
+
+```typescript
+function memoObject<
+ GenericOutput extends object
+>(
+ getter: () => GenericOutput
+): GenericOutput;
+```
+
+## Parameters
+
+- `getter` : Function called on first access to produce the proxied object.
+
+## Return value
+
+A proxied `GenericOutput` object:
+- reads (`obj.prop`) return values from the memoized object;
+- writes (`obj.prop = value`) mutate the memoized object;
+- `Object.keys()` and the `in` operator reflect keys after writes.
+
+## See also
+
+- [`memo`](/en/v1/api/common/memo) - Lazy memoization for synchronous values
+- [`memoPromise`](/en/v1/api/common/memoPromise) - Lazy memoization for async-capable values
+- [`override`](/en/v1/api/common/override) - Override methods and default properties on an object
diff --git a/docs/en/v1/api/common/memoPromise.md b/docs/en/v1/api/common/memoPromise.md
index 157f8c8d..ab6f15d1 100644
--- a/docs/en/v1/api/common/memoPromise.md
+++ b/docs/en/v1/api/common/memoPromise.md
@@ -2,8 +2,8 @@
outline: [2, 3]
description: "The memoPromise() function lazily evaluates a function that returns a value or a promise, then memoizes the resolved result."
prev:
- text: "memo"
- link: "/en/v1/api/common/memo"
+ text: "memoObject"
+ link: "/en/v1/api/common/memoObject"
next:
text: "stringToMillisecond"
link: "/en/v1/api/common/stringToMillisecond"
diff --git a/docs/examples/v1/api/clean/flag/tryout.doc.ts b/docs/examples/v1/api/clean/flag/tryout.doc.ts
index bead46a3..37e26a07 100644
--- a/docs/examples/v1/api/clean/flag/tryout.doc.ts
+++ b/docs/examples/v1/api/clean/flag/tryout.doc.ts
@@ -16,14 +16,14 @@ namespace User {
export const MajorFlag = C.createFlag<
Entity, // mandatory
"majorUser", // mandatory
- Age // optional
+ { age: Age } // optional
>("majorUser");
export type MajorFlag = C.GetFlag;
export function isMajor(entity: Entity) {
if (C.greaterThan(entity.age, 18)) {
return E.success(
- MajorFlag.append(entity, entity.age),
+ MajorFlag.append(entity, { age: entity.age }),
);
}
return E.left("not-major");
diff --git a/docs/examples/v1/api/clean/unwrapEntity/tryout.doc.ts b/docs/examples/v1/api/clean/unwrapEntity/tryout.doc.ts
index 2b214703..ebb83936 100644
--- a/docs/examples/v1/api/clean/unwrapEntity/tryout.doc.ts
+++ b/docs/examples/v1/api/clean/unwrapEntity/tryout.doc.ts
@@ -21,7 +21,7 @@ export namespace User {
export const IsAdmin = C.createFlag<
Entity,
"isAdmin",
- boolean
+ { value: boolean }
>("isAdmin");
export type IsAdmin = C.GetFlag;
}
@@ -33,7 +33,7 @@ const user = User.Entity.new({
createdAt: User.CreatedAt.createOrThrow(D.now()),
});
-const flaggedUser = User.IsAdmin.append(user, true);
+const flaggedUser = User.IsAdmin.append(user, { value: true });
const unwrappedUser = C.unwrapEntity(flaggedUser);
type check = ExpectType<
diff --git a/docs/examples/v1/api/common/memoObject/tryout.doc.ts b/docs/examples/v1/api/common/memoObject/tryout.doc.ts
new file mode 100644
index 00000000..ff8b3a10
--- /dev/null
+++ b/docs/examples/v1/api/common/memoObject/tryout.doc.ts
@@ -0,0 +1,15 @@
+import { memoObject } from "@duplojs/utils";
+
+let calls = 0;
+const state = memoObject(() => {
+ calls += 1;
+ return { count: 1 };
+});
+
+const first = state.count;
+const second = state.count;
+// calls = 1
+
+state.count = 2;
+const updated = state.count;
+// updated = 2
diff --git a/docs/fr/v1/api/common/index.md b/docs/fr/v1/api/common/index.md
index bb66909c..1c8f9113 100644
--- a/docs/fr/v1/api/common/index.md
+++ b/docs/fr/v1/api/common/index.md
@@ -127,6 +127,9 @@ Pause asynchrone pour attendre un certain temps.
### [memo](/fr/v1/api/common/memo)
Évalue une fonction une seule fois et réutilise le résultat (memoization lazy).
+### [memoObject](/fr/v1/api/common/memoObject)
+Mémoïse un objet exposé via `Proxy` et garde les clés alignées avec les écritures.
+
### [memoPromise](/fr/v1/api/common/memoPromise)
Mémoïsation paresseuse pour des fonctions retournant une valeur ou une promesse.
diff --git a/docs/fr/v1/api/common/memo.md b/docs/fr/v1/api/common/memo.md
index 9948d7b4..cc5b4c04 100644
--- a/docs/fr/v1/api/common/memo.md
+++ b/docs/fr/v1/api/common/memo.md
@@ -5,8 +5,8 @@ prev:
text: "sleep"
link: "/fr/v1/api/common/sleep"
next:
- text: "memoPromise"
- link: "/fr/v1/api/common/memoPromise"
+ text: "memoObject"
+ link: "/fr/v1/api/common/memoObject"
---
# memo
diff --git a/docs/fr/v1/api/common/memoObject.md b/docs/fr/v1/api/common/memoObject.md
new file mode 100644
index 00000000..2af0131a
--- /dev/null
+++ b/docs/fr/v1/api/common/memoObject.md
@@ -0,0 +1,49 @@
+---
+outline: [2, 3]
+description: "La fonction memoObject() crée un proxy autour d'un objet mémorisé. Le getter n'est évalué qu'une fois, puis les lectures/écritures passent par la même référence."
+prev:
+ text: "memo"
+ link: "/fr/v1/api/common/memo"
+next:
+ text: "memoPromise"
+ link: "/fr/v1/api/common/memoPromise"
+---
+
+# memoObject
+
+La fonction **`memoObject()`** crée un proxy autour d'un objet mémorisé. Le getter est évalué paresseusement au premier accès, puis toutes les lectures/écritures utilisent le même objet.
+
+## Exemple interactif
+
+
+
+## Syntaxe
+
+```typescript
+function memoObject<
+ GenericOutput extends object
+>(
+ getter: () => GenericOutput
+): GenericOutput;
+```
+
+## Paramètres
+
+- `getter` : Fonction appelée au premier accès pour produire l'objet proxifié.
+
+## Valeur de retour
+
+Un objet de type `GenericOutput` proxifié :
+- les lectures (`obj.prop`) renvoient les valeurs de l'objet mémorisé ;
+- les écritures (`obj.prop = value`) modifient l'objet mémorisé ;
+- `Object.keys()` et l'opérateur `in` reflètent les clés après écriture.
+
+## Voir aussi
+
+- [`memo`](/fr/v1/api/common/memo) - Mémoïsation paresseuse synchrone
+- [`memoPromise`](/fr/v1/api/common/memoPromise) - Mémoïsation paresseuse pour valeurs async
+- [`override`](/fr/v1/api/common/override) - Surcharger méthodes et propriétés d'un objet
diff --git a/docs/fr/v1/api/common/memoPromise.md b/docs/fr/v1/api/common/memoPromise.md
index 287fd61b..253b4b2a 100644
--- a/docs/fr/v1/api/common/memoPromise.md
+++ b/docs/fr/v1/api/common/memoPromise.md
@@ -2,8 +2,8 @@
outline: [2, 3]
description: "La fonction memoPromise() évalue paresseusement une fonction qui retourne une valeur ou une promesse, puis mémorise le résultat résolu."
prev:
- text: "memo"
- link: "/fr/v1/api/common/memo"
+ text: "memoObject"
+ link: "/fr/v1/api/common/memoObject"
next:
text: "stringToMillisecond"
link: "/fr/v1/api/common/stringToMillisecond"
diff --git a/docs/public/libs/v1/clean/entity/index.cjs b/docs/public/libs/v1/clean/entity/index.cjs
index 93b42543..34dceaf5 100644
--- a/docs/public/libs/v1/clean/entity/index.cjs
+++ b/docs/public/libs/v1/clean/entity/index.cjs
@@ -5,21 +5,22 @@ var newType = require('../newType.cjs');
var property = require('./property.cjs');
var kind$1 = require('../../common/kind.cjs');
var pipe = require('../../common/pipe.cjs');
-var map = require('../../array/map.cjs');
-var entry = require('../../object/entry.cjs');
-var base = require('../constraint/base.cjs');
-var transform = require('../../dataParser/parsers/transform.cjs');
-var entries = require('../../object/entries.cjs');
-var forward = require('../../common/forward.cjs');
var errorKindNamespace = require('../../common/errorKindNamespace.cjs');
-var index = require('../../dataParser/parsers/object/index.cjs');
-var fromEntries = require('../../object/fromEntries.cjs');
-var wrapValue = require('../../common/wrapValue.cjs');
+var memo = require('../../common/memo.cjs');
var override = require('../../common/override.cjs');
var is = require('../../either/left/is.cjs');
var unwrap$1 = require('../../common/unwrap.cjs');
var create = require('../../either/left/create.cjs');
var create$1 = require('../../either/right/create.cjs');
+var transform = require('../../dataParser/parsers/transform.cjs');
+var index = require('../../dataParser/parsers/object/index.cjs');
+var fromEntries = require('../../object/fromEntries.cjs');
+var map = require('../../array/map.cjs');
+var entry = require('../../object/entry.cjs');
+var base = require('../constraint/base.cjs');
+var wrapValue = require('../../common/wrapValue.cjs');
+var entries = require('../../object/entries.cjs');
+var forward = require('../../common/forward.cjs');
const entityKind = kind.createCleanKind("entity");
const entityHandlerKind = kind.createCleanKind("entity-handler");
@@ -39,8 +40,8 @@ function createEntity(name, getPropertiesDefinition) {
function theNew(properties) {
return entityKind.addTo(properties, name);
}
- const propertiesDefinition = getPropertiesDefinition(property.entityPropertyDefinitionTools);
- const mapDataParser = pipe.pipe(forward.forward(propertiesDefinition), entries.entries, map.map(([key, property$1]) => entry.entry(key, property.entityPropertyDefinitionToDataParser(property$1, (newTypeHandler) => {
+ const propertiesDefinition = memo.memo(() => getPropertiesDefinition(property.entityPropertyDefinitionTools));
+ const mapDataParser = memo.memo(() => pipe.pipe(forward.forward(propertiesDefinition.value), entries.entries, map.map(([key, property$1]) => entry.entry(key, property.entityPropertyDefinitionToDataParser(property$1, (newTypeHandler) => {
const allKind = {
...base.constrainedTypeKind.setTo({}, newTypeHandler.internal.constraintKindValue),
...newType.newTypeKind.setTo({}, newTypeHandler.name),
@@ -49,16 +50,16 @@ function createEntity(name, getPropertiesDefinition) {
...allKind,
[wrapValue.keyWrappedValue]: value,
}));
- }))), fromEntries.fromEntries, index.object, (dataParser) => transform.transform(dataParser, (value) => entityKind.setTo(value, name)));
+ }))), fromEntries.fromEntries, index.object, (dataParser) => transform.transform(dataParser, (value) => entityKind.setTo(value, name))));
function map$1(rawProperties) {
- const result = mapDataParser.parse(rawProperties);
+ const result = mapDataParser.value.parse(rawProperties);
if (is.isLeft(result)) {
return create.left("createEntityError", unwrap$1.unwrap(result));
}
return create$1.right("createEntity", unwrap$1.unwrap(result));
}
function mapOrThrow(rawProperties) {
- const result = mapDataParser.parse(rawProperties);
+ const result = mapDataParser.value.parse(rawProperties);
if (is.isLeft(result)) {
throw new CreateEntityError(rawProperties, unwrap$1.unwrap(result));
}
@@ -67,9 +68,14 @@ function createEntity(name, getPropertiesDefinition) {
function is$1(input) {
return entityKind.has(input) && entityKind.getValue(input) === name;
}
- function update(entity, newProperties) {
+ function update(...args) {
+ if (args.length === 1) {
+ const [newProperties] = args;
+ return (entity) => update(entity, newProperties);
+ }
+ const [entity, newProperties] = args;
const updatedEntity = {};
- for (const key in propertiesDefinition) {
+ for (const key in propertiesDefinition.value) {
updatedEntity[key] = newProperties[key] !== undefined
? newProperties[key]
: entity[key];
@@ -78,10 +84,16 @@ function createEntity(name, getPropertiesDefinition) {
}
return pipe.pipe({
name,
- propertiesDefinition,
- mapDataParser,
+ get propertiesDefinition() {
+ return propertiesDefinition.value;
+ },
+ get mapDataParser() {
+ return mapDataParser.value;
+ },
internal: {
- mapDataParser,
+ get mapDataParser() {
+ return mapDataParser.value;
+ },
},
new: theNew,
map: map$1,
diff --git a/docs/public/libs/v1/clean/entity/index.d.ts b/docs/public/libs/v1/clean/entity/index.d.ts
index 1fe4e099..ba0959d8 100644
--- a/docs/public/libs/v1/clean/entity/index.d.ts
+++ b/docs/public/libs/v1/clean/entity/index.d.ts
@@ -112,6 +112,7 @@ export interface EntityHandler, const GenericProperties extends Partial>>(properties: GenericProperties): (entity: GenericEntity) => EntityUpdate;
update, const GenericProperties extends Partial>>(entity: GenericEntity, properties: GenericProperties): EntityUpdate;
}
declare const CreateEntityError_base: new (params: {
diff --git a/docs/public/libs/v1/clean/entity/index.mjs b/docs/public/libs/v1/clean/entity/index.mjs
index fd147a08..3381557b 100644
--- a/docs/public/libs/v1/clean/entity/index.mjs
+++ b/docs/public/libs/v1/clean/entity/index.mjs
@@ -1,24 +1,25 @@
import { createCleanKind } from '../kind.mjs';
import { newTypeKind } from '../newType.mjs';
-import { entityPropertyDefinitionTools, entityPropertyDefinitionToDataParser } from './property.mjs';
+import { entityPropertyDefinitionToDataParser, entityPropertyDefinitionTools } from './property.mjs';
export { entityPropertyArrayKind, entityPropertyIdentifierKind, entityPropertyNullableKind, entityPropertyStructureKind, entityPropertyUnionKind } from './property.mjs';
import { kindHeritage } from '../../common/kind.mjs';
import { pipe } from '../../common/pipe.mjs';
-import { map } from '../../array/map.mjs';
-import { entry } from '../../object/entry.mjs';
-import { constrainedTypeKind } from '../constraint/base.mjs';
-import { transform } from '../../dataParser/parsers/transform.mjs';
-import { entries } from '../../object/entries.mjs';
-import { forward } from '../../common/forward.mjs';
import { createErrorKind } from '../../common/errorKindNamespace.mjs';
-import { object } from '../../dataParser/parsers/object/index.mjs';
-import { fromEntries } from '../../object/fromEntries.mjs';
-import { keyWrappedValue } from '../../common/wrapValue.mjs';
+import { memo } from '../../common/memo.mjs';
import { createOverride } from '../../common/override.mjs';
import { isLeft } from '../../either/left/is.mjs';
import { unwrap } from '../../common/unwrap.mjs';
import { left } from '../../either/left/create.mjs';
import { right } from '../../either/right/create.mjs';
+import { transform } from '../../dataParser/parsers/transform.mjs';
+import { object } from '../../dataParser/parsers/object/index.mjs';
+import { fromEntries } from '../../object/fromEntries.mjs';
+import { map } from '../../array/map.mjs';
+import { entry } from '../../object/entry.mjs';
+import { constrainedTypeKind } from '../constraint/base.mjs';
+import { keyWrappedValue } from '../../common/wrapValue.mjs';
+import { entries } from '../../object/entries.mjs';
+import { forward } from '../../common/forward.mjs';
const entityKind = createCleanKind("entity");
const entityHandlerKind = createCleanKind("entity-handler");
@@ -38,8 +39,8 @@ function createEntity(name, getPropertiesDefinition) {
function theNew(properties) {
return entityKind.addTo(properties, name);
}
- const propertiesDefinition = getPropertiesDefinition(entityPropertyDefinitionTools);
- const mapDataParser = pipe(forward(propertiesDefinition), entries, map(([key, property]) => entry(key, entityPropertyDefinitionToDataParser(property, (newTypeHandler) => {
+ const propertiesDefinition = memo(() => getPropertiesDefinition(entityPropertyDefinitionTools));
+ const mapDataParser = memo(() => pipe(forward(propertiesDefinition.value), entries, map(([key, property]) => entry(key, entityPropertyDefinitionToDataParser(property, (newTypeHandler) => {
const allKind = {
...constrainedTypeKind.setTo({}, newTypeHandler.internal.constraintKindValue),
...newTypeKind.setTo({}, newTypeHandler.name),
@@ -48,16 +49,16 @@ function createEntity(name, getPropertiesDefinition) {
...allKind,
[keyWrappedValue]: value,
}));
- }))), fromEntries, object, (dataParser) => transform(dataParser, (value) => entityKind.setTo(value, name)));
+ }))), fromEntries, object, (dataParser) => transform(dataParser, (value) => entityKind.setTo(value, name))));
function map$1(rawProperties) {
- const result = mapDataParser.parse(rawProperties);
+ const result = mapDataParser.value.parse(rawProperties);
if (isLeft(result)) {
return left("createEntityError", unwrap(result));
}
return right("createEntity", unwrap(result));
}
function mapOrThrow(rawProperties) {
- const result = mapDataParser.parse(rawProperties);
+ const result = mapDataParser.value.parse(rawProperties);
if (isLeft(result)) {
throw new CreateEntityError(rawProperties, unwrap(result));
}
@@ -66,9 +67,14 @@ function createEntity(name, getPropertiesDefinition) {
function is(input) {
return entityKind.has(input) && entityKind.getValue(input) === name;
}
- function update(entity, newProperties) {
+ function update(...args) {
+ if (args.length === 1) {
+ const [newProperties] = args;
+ return (entity) => update(entity, newProperties);
+ }
+ const [entity, newProperties] = args;
const updatedEntity = {};
- for (const key in propertiesDefinition) {
+ for (const key in propertiesDefinition.value) {
updatedEntity[key] = newProperties[key] !== undefined
? newProperties[key]
: entity[key];
@@ -77,10 +83,16 @@ function createEntity(name, getPropertiesDefinition) {
}
return pipe({
name,
- propertiesDefinition,
- mapDataParser,
+ get propertiesDefinition() {
+ return propertiesDefinition.value;
+ },
+ get mapDataParser() {
+ return mapDataParser.value;
+ },
internal: {
- mapDataParser,
+ get mapDataParser() {
+ return mapDataParser.value;
+ },
},
new: theNew,
map: map$1,
diff --git a/docs/public/libs/v1/clean/flag.cjs b/docs/public/libs/v1/clean/flag.cjs
index dd3f5d6d..b22d56af 100644
--- a/docs/public/libs/v1/clean/flag.cjs
+++ b/docs/public/libs/v1/clean/flag.cjs
@@ -1,5 +1,6 @@
'use strict';
+var index = require('./entity/index.cjs');
var kind = require('./kind.cjs');
var pipe = require('../common/pipe.cjs');
var override = require('../common/override.cjs');
@@ -10,17 +11,21 @@ const flagKind = kind.createCleanKind("flag");
* {@include clean/createFlag/index.md}
*/
function createFlag(name) {
+ function append(maybeEntity, value) {
+ if (!index.entityKind.has(maybeEntity)) {
+ return (entity) => append(entity, maybeEntity);
+ }
+ const flagValue = flagKind.has(maybeEntity)
+ ? {
+ ...flagKind.getValue(maybeEntity),
+ [name]: value,
+ }
+ : { [name]: value };
+ return flagKind.addTo(maybeEntity, flagValue);
+ }
return pipe.pipe({
name,
- append(entity, value) {
- const flagValue = flagKind.has(entity)
- ? {
- ...flagKind.getValue(entity),
- [name]: value,
- }
- : { [name]: value };
- return flagKind.addTo(entity, flagValue);
- },
+ append,
getValue(entity) {
return flagKind.getValue(entity)[name];
},
diff --git a/docs/public/libs/v1/clean/flag.d.ts b/docs/public/libs/v1/clean/flag.d.ts
index 09cfaebe..92a80486 100644
--- a/docs/public/libs/v1/clean/flag.d.ts
+++ b/docs/public/libs/v1/clean/flag.d.ts
@@ -2,7 +2,7 @@ import { type Kind, type IsEqual, type Or, type GetKindValue } from "../common";
import { type Entity } from "./entity";
declare const flagHandlerKind: import("../common").KindHandler>;
export declare const flagKind: import("../common").KindHandler>>;
-export interface FlagHandler extends Kind {
+export interface FlagHandler = never> extends Kind {
/**
* The flag name stored as the key on the entity.
*
@@ -20,7 +20,8 @@ export interface FlagHandler(entity: GenericInputEntity, ...args: IsEqual extends true ? [] : [GenericInputValue]): (GenericInputEntity & Flag);
+ append(...args: IsEqual extends true ? [] : [GenericInputValue]): (entity: GenericInputEntity) => (GenericInputEntity & Flag);
+ append(entity: GenericInputEntity, ...args: IsEqual extends true ? [] : [GenericInputValue]): (GenericInputEntity & Flag);
/**
* Retrieves the value carried by the flag.
*
@@ -106,7 +107,7 @@ export interface Flag(name: Or<[
+export declare function createFlag = never>(name: Or<[
IsEqual,
IsEqual
]> extends true ? never : NoInfer): FlagHandler;
diff --git a/docs/public/libs/v1/clean/flag.mjs b/docs/public/libs/v1/clean/flag.mjs
index 875d0015..f18bb290 100644
--- a/docs/public/libs/v1/clean/flag.mjs
+++ b/docs/public/libs/v1/clean/flag.mjs
@@ -1,3 +1,4 @@
+import { entityKind } from './entity/index.mjs';
import { createCleanKind } from './kind.mjs';
import { pipe } from '../common/pipe.mjs';
import { createOverride } from '../common/override.mjs';
@@ -8,17 +9,21 @@ const flagKind = createCleanKind("flag");
* {@include clean/createFlag/index.md}
*/
function createFlag(name) {
+ function append(maybeEntity, value) {
+ if (!entityKind.has(maybeEntity)) {
+ return (entity) => append(entity, maybeEntity);
+ }
+ const flagValue = flagKind.has(maybeEntity)
+ ? {
+ ...flagKind.getValue(maybeEntity),
+ [name]: value,
+ }
+ : { [name]: value };
+ return flagKind.addTo(maybeEntity, flagValue);
+ }
return pipe({
name,
- append(entity, value) {
- const flagValue = flagKind.has(entity)
- ? {
- ...flagKind.getValue(entity),
- [name]: value,
- }
- : { [name]: value };
- return flagKind.addTo(entity, flagValue);
- },
+ append,
getValue(entity) {
return flagKind.getValue(entity)[name];
},
diff --git a/docs/public/libs/v1/common/index.d.ts b/docs/public/libs/v1/common/index.d.ts
index 98df42d0..15b9a557 100644
--- a/docs/public/libs/v1/common/index.d.ts
+++ b/docs/public/libs/v1/common/index.d.ts
@@ -58,6 +58,7 @@ export * from "./or";
export * from "./whenElse";
export * from "./justReturn";
export * from "./memo";
+export * from "./memoObject";
export * from "./memoPromise";
export * from "./instanceOf";
export * from "./globalStore";
diff --git a/docs/public/libs/v1/common/memoObject.cjs b/docs/public/libs/v1/common/memoObject.cjs
new file mode 100644
index 00000000..775f29bb
--- /dev/null
+++ b/docs/public/libs/v1/common/memoObject.cjs
@@ -0,0 +1,33 @@
+'use strict';
+
+var memo = require('./memo.cjs');
+
+/**
+ * {@include common/memoObject/index.md}
+ */
+function memoObject(getter) {
+ const memoizedValue = memo.memo(getter);
+ let memoizedKeys = memo.memo(() => Object.keys(memoizedValue.value));
+ return new Proxy({}, {
+ get: (_target, prop) => memoizedValue.value[prop],
+ set: (_target, prop, value) => {
+ memoizedValue.value[prop] = value;
+ memoizedKeys = memo.memo(() => Object.keys(memoizedValue.value));
+ return true;
+ },
+ ownKeys() {
+ return memoizedKeys.value;
+ },
+ has(_target, prop) {
+ return memoizedKeys.value.includes(prop);
+ },
+ getOwnPropertyDescriptor() {
+ return {
+ enumerable: true,
+ configurable: true,
+ };
+ },
+ });
+}
+
+exports.memoObject = memoObject;
diff --git a/docs/public/libs/v1/common/memoObject.d.ts b/docs/public/libs/v1/common/memoObject.d.ts
new file mode 100644
index 00000000..4764d1f1
--- /dev/null
+++ b/docs/public/libs/v1/common/memoObject.d.ts
@@ -0,0 +1,29 @@
+/**
+ * The memoObject() function lazily evaluates a getter, memoizes the returned object, and exposes it through a proxy.
+ *
+ * Call style: direct call (`memoObject(getter)`).
+ *
+ * Reads and writes go through the same memoized object.
+ *
+ * ```ts
+ * let calls = 0;
+ * const state = memoObject(() => {
+ * calls += 1;
+ * return {
+ * count: 1,
+ * };
+ * });
+ *
+ * const first = state.count;
+ * const second = state.count;
+ * // calls = 1
+ *
+ * state.count = 2;
+ * const updated = state.count;
+ * // updated = 2
+ * ```
+ *
+ * @see https://utils.duplojs.dev/en/v1/api/common/memoObject
+ *
+ */
+export declare function memoObject(getter: () => GenericOutput): GenericOutput;
diff --git a/docs/public/libs/v1/common/memoObject.mjs b/docs/public/libs/v1/common/memoObject.mjs
new file mode 100644
index 00000000..edcccddd
--- /dev/null
+++ b/docs/public/libs/v1/common/memoObject.mjs
@@ -0,0 +1,31 @@
+import { memo } from './memo.mjs';
+
+/**
+ * {@include common/memoObject/index.md}
+ */
+function memoObject(getter) {
+ const memoizedValue = memo(getter);
+ let memoizedKeys = memo(() => Object.keys(memoizedValue.value));
+ return new Proxy({}, {
+ get: (_target, prop) => memoizedValue.value[prop],
+ set: (_target, prop, value) => {
+ memoizedValue.value[prop] = value;
+ memoizedKeys = memo(() => Object.keys(memoizedValue.value));
+ return true;
+ },
+ ownKeys() {
+ return memoizedKeys.value;
+ },
+ has(_target, prop) {
+ return memoizedKeys.value.includes(prop);
+ },
+ getOwnPropertyDescriptor() {
+ return {
+ enumerable: true,
+ configurable: true,
+ };
+ },
+ });
+}
+
+export { memoObject };
diff --git a/docs/public/libs/v1/dataParser/extended/string.d.ts b/docs/public/libs/v1/dataParser/extended/string.d.ts
index 0029544b..bf0eb44f 100644
--- a/docs/public/libs/v1/dataParser/extended/string.d.ts
+++ b/docs/public/libs/v1/dataParser/extended/string.d.ts
@@ -216,7 +216,7 @@ export declare function url(definition?: Partial("majorUser");
export type MajorFlag = C.GetFlag;
export function isMajor(entity: Entity) {
if (C.greaterThan(entity.age, 18)) {
return E.success(
- MajorFlag.append(entity, entity.age),
+ MajorFlag.append(entity, { age: entity.age }),
);
}
return E.left("not-major");
@@ -49,5 +49,5 @@ const result = pipe(
);
// E.Left<"not-major", undefined> | E.Right<"not-thirsty-anymore", undefined>
-const flagged = User.MajorFlag.append(user, user.age);
+const flagged = User.MajorFlag.append(user, { age: user.age });
const value = User.MajorFlag.getValue(flagged);
diff --git a/jsDoc/common/memoObject/example.ts b/jsDoc/common/memoObject/example.ts
new file mode 100644
index 00000000..c9a047dd
--- /dev/null
+++ b/jsDoc/common/memoObject/example.ts
@@ -0,0 +1,17 @@
+import { memoObject } from "@scripts";
+
+let calls = 0;
+const state = memoObject(() => {
+ calls += 1;
+ return {
+ count: 1,
+ };
+});
+
+const first = state.count;
+const second = state.count;
+// calls = 1
+
+state.count = 2;
+const updated = state.count;
+// updated = 2
diff --git a/jsDoc/common/memoObject/index.md b/jsDoc/common/memoObject/index.md
new file mode 100644
index 00000000..8981b6ca
--- /dev/null
+++ b/jsDoc/common/memoObject/index.md
@@ -0,0 +1,11 @@
+The memoObject() function lazily evaluates a getter, memoizes the returned object, and exposes it through a proxy.
+
+Call style: direct call (`memoObject(getter)`).
+
+Reads and writes go through the same memoized object.
+
+```ts
+{@include common/memoObject/example.ts[3,17]}
+```
+
+@see https://utils.duplojs.dev/en/v1/api/common/memoObject
diff --git a/scripts/clean/entity/index.ts b/scripts/clean/entity/index.ts
index 6644bde1..1e267265 100644
--- a/scripts/clean/entity/index.ts
+++ b/scripts/clean/entity/index.ts
@@ -1,4 +1,4 @@
-import { type SimplifyTopLevel, type Kind, unwrap, kindHeritage, createErrorKind, pipe, forward, type RemoveKind, type RemoveReadonly, createOverride, type AnyFunction, type GetKindValue, keyWrappedValue } from "@scripts/common";
+import { type SimplifyTopLevel, type Kind, unwrap, kindHeritage, createErrorKind, pipe, forward, type RemoveKind, type RemoveReadonly, createOverride, type AnyFunction, type GetKindValue, keyWrappedValue, memo } from "@scripts/common";
import { createCleanKind } from "../kind";
import { newTypeKind } from "../newType";
import { constrainedTypeKind } from "../constraint";
@@ -138,6 +138,13 @@ export interface EntityHandler<
/**
* {@include clean/createEntity/update.md}
*/
+ update<
+ const GenericEntity extends Entity,
+ const GenericProperties extends Partial>,
+ >(
+ properties: GenericProperties,
+ ): (entity: GenericEntity) => EntityUpdate;
+
update<
const GenericEntity extends Entity,
const GenericProperties extends Partial>,
@@ -182,49 +189,53 @@ export function createEntity<
return entityKind.addTo(properties, name);
}
- const propertiesDefinition = getPropertiesDefinition(entityPropertyDefinitionTools);
-
- const mapDataParser = pipe(
- forward(propertiesDefinition),
- DObject.entries,
- DArray.map(
- ([key, property]) => DObject.entry(
- key,
- entityPropertyDefinitionToDataParser(
- property,
- (newTypeHandler) => {
- const allKind = {
- ...constrainedTypeKind.setTo(
- {},
- newTypeHandler.internal.constraintKindValue,
- ),
- ...newTypeKind.setTo(
- {},
- newTypeHandler.name,
- ),
- };
-
- return DDataParser.transform(
- newTypeHandler.internal.dataParser,
- (value) => ({
- ...allKind,
- [keyWrappedValue]: value,
- }),
- );
- },
+ const propertiesDefinition = memo(
+ () => getPropertiesDefinition(entityPropertyDefinitionTools),
+ );
+
+ const mapDataParser = memo(
+ () => pipe(
+ forward(propertiesDefinition.value),
+ DObject.entries,
+ DArray.map(
+ ([key, property]) => DObject.entry(
+ key,
+ entityPropertyDefinitionToDataParser(
+ property,
+ (newTypeHandler) => {
+ const allKind = {
+ ...constrainedTypeKind.setTo(
+ {},
+ newTypeHandler.internal.constraintKindValue,
+ ),
+ ...newTypeKind.setTo(
+ {},
+ newTypeHandler.name,
+ ),
+ };
+
+ return DDataParser.transform(
+ newTypeHandler.internal.dataParser,
+ (value) => ({
+ ...allKind,
+ [keyWrappedValue]: value,
+ }),
+ );
+ },
+ ),
),
),
- ),
- DObject.fromEntries,
- DDataParser.object,
- (dataParser) => DDataParser.transform(
- dataParser,
- (value) => entityKind.setTo(value, name),
+ DObject.fromEntries,
+ DDataParser.object,
+ (dataParser) => DDataParser.transform(
+ dataParser,
+ (value) => entityKind.setTo(value, name),
+ ),
),
);
function map(rawProperties: PropertiesToMapOfEntity) {
- const result = mapDataParser.parse(rawProperties);
+ const result = mapDataParser.value.parse(rawProperties);
if (DEither.isLeft(result)) {
return DEither.left(
@@ -240,7 +251,7 @@ export function createEntity<
}
function mapOrThrow(rawProperties: PropertiesToMapOfEntity) {
- const result = mapDataParser.parse(rawProperties);
+ const result = mapDataParser.value.parse(rawProperties);
if (DEither.isLeft(result)) {
throw new CreateEntityError(rawProperties, unwrap(result));
@@ -254,12 +265,17 @@ export function createEntity<
}
function update(
- entity: EntityProperties,
- newProperties: Partial,
+ ...args: [Partial]
+ | [EntityProperties, Partial]
) {
+ if (args.length === 1) {
+ const [newProperties] = args;
+ return (entity: EntityProperties) => update(entity, newProperties);
+ }
+ const [entity, newProperties] = args;
const updatedEntity: RemoveReadonly = {};
- for (const key in propertiesDefinition) {
+ for (const key in propertiesDefinition.value) {
updatedEntity[key] = newProperties[key] !== undefined
? newProperties[key]
: entity[key];
@@ -271,10 +287,16 @@ export function createEntity<
return pipe(
{
name,
- propertiesDefinition,
- mapDataParser,
+ get propertiesDefinition() {
+ return propertiesDefinition.value;
+ },
+ get mapDataParser() {
+ return mapDataParser.value;
+ },
internal: {
- mapDataParser,
+ get mapDataParser() {
+ return mapDataParser.value;
+ },
},
new: theNew,
map,
diff --git a/scripts/clean/flag.ts b/scripts/clean/flag.ts
index b097129c..323b01c8 100644
--- a/scripts/clean/flag.ts
+++ b/scripts/clean/flag.ts
@@ -1,5 +1,5 @@
-import { type Kind, type IsEqual, type Or, type GetKindValue, createOverride, pipe, type AnyFunction, type RemoveKind } from "@scripts/common";
-import { type Entity } from "./entity";
+import { type Kind, type IsEqual, type Or, type GetKindValue, createOverride, pipe, type AnyFunction, type RemoveKind, type AnyValue } from "@scripts/common";
+import { entityKind, type Entity } from "./entity";
import { createCleanKind } from "./kind";
const flagHandlerKind = createCleanKind("flag-handler");
@@ -12,7 +12,7 @@ export const flagKind = createCleanKind<
export interface FlagHandler<
GenericEntity extends Entity = Entity,
GenericName extends string = string,
- GenericValue extends unknown = never,
+ GenericValue extends Record = never,
> extends Kind {
/**
@@ -25,7 +25,22 @@ export interface FlagHandler<
*/
append<
GenericInputEntity extends GenericEntity,
- GenericInputValue extends GenericValue,
+ const GenericInputValue extends GenericValue,
+ >(
+ ...args: IsEqual<
+ GenericValue,
+ never
+ > extends true
+ ? []
+ : [GenericInputValue]
+ ): (entity: GenericInputEntity) => (
+ & GenericInputEntity
+ & Flag
+ );
+
+ append<
+ GenericInputEntity extends GenericEntity,
+ const GenericInputValue extends GenericValue,
>(
entity: GenericInputEntity,
...args: IsEqual<
@@ -77,7 +92,7 @@ export interface Flag<
export function createFlag<
GenericEntity extends Entity = never,
GenericName extends string = never,
- GenericValue extends unknown = never,
+ GenericValue extends Record = never,
>(
name: Or<[
IsEqual,
@@ -90,22 +105,28 @@ export function createFlag<
GenericName,
GenericValue
> {
+ function append(maybeEntity: Entity | AnyValue, value: any) {
+ if (!entityKind.has(maybeEntity)) {
+ return (entity: Entity) => append(entity, maybeEntity);
+ }
+
+ const flagValue = flagKind.has(maybeEntity)
+ ? {
+ ...(flagKind.getValue(maybeEntity) as object),
+ [name]: value,
+ }
+ : { [name]: value };
+
+ return flagKind.addTo(
+ maybeEntity,
+ flagValue,
+ );
+ }
+
return pipe(
{
name,
- append(entity: Entity, value: any) {
- const flagValue = flagKind.has(entity)
- ? {
- ...(flagKind.getValue(entity) as object),
- [name]: value,
- }
- : { [name]: value };
-
- return flagKind.addTo(
- entity,
- flagValue,
- );
- },
+ append,
getValue(entity: Entity) {
return flagKind.getValue(entity as never)[name];
},
diff --git a/scripts/common/index.ts b/scripts/common/index.ts
index 9b04da74..c4ec315c 100644
--- a/scripts/common/index.ts
+++ b/scripts/common/index.ts
@@ -37,6 +37,7 @@ export * from "./or";
export * from "./whenElse";
export * from "./justReturn";
export * from "./memo";
+export * from "./memoObject";
export * from "./memoPromise";
export * from "./instanceOf";
export * from "./globalStore";
diff --git a/scripts/common/memoObject.ts b/scripts/common/memoObject.ts
new file mode 100644
index 00000000..36765543
--- /dev/null
+++ b/scripts/common/memoObject.ts
@@ -0,0 +1,38 @@
+import { memo } from "./memo";
+
+/**
+ * {@include common/memoObject/index.md}
+ */
+export function memoObject<
+ GenericOutput extends object,
+>(
+ getter: () => GenericOutput,
+): GenericOutput {
+ const memoizedValue = memo