joist-core 2.4.0-next.55 → 2.4.0-next.56
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/build/fields.cjs +1 -1
- package/build/fields.cjs.map +1 -1
- package/build/fields.d.cts.map +1 -1
- package/build/fields.d.mts.map +1 -1
- package/build/fields.js +1 -1
- package/build/fields.js.map +1 -1
- package/package.json +2 -2
package/build/fields.cjs
CHANGED
|
@@ -55,7 +55,6 @@ function setField(entity, fieldName, newValue) {
|
|
|
55
55
|
const api = require_EntityManager.getEmInternalApi(em);
|
|
56
56
|
const { rm, pluginManager, indexManager, fieldLogger, isLoadedCache } = api;
|
|
57
57
|
api.checkWritesAllowed();
|
|
58
|
-
isLoadedCache.resetIsLoaded(entity, fieldName);
|
|
59
58
|
const instanceData = require_BaseEntity.getInstanceData(entity);
|
|
60
59
|
const { data, originalData, flushedData } = instanceData;
|
|
61
60
|
const field = require_EntityMetadata.getMetadata(entity).allFields[fieldName];
|
|
@@ -63,6 +62,7 @@ function setField(entity, fieldName, newValue) {
|
|
|
63
62
|
const currentValue = getField(entity, fieldName, true);
|
|
64
63
|
const isReference = field.kind === "m2o" || field.kind === "poly";
|
|
65
64
|
const currentEqualsNew = fieldValueEquals(field, currentValue, newValue);
|
|
65
|
+
if (!currentEqualsNew) isLoadedCache.resetIsLoaded(entity, fieldName);
|
|
66
66
|
if (field.kind === "enum" && !currentEqualsNew) rm.queueTransition(entity, fieldName, currentValue, newValue);
|
|
67
67
|
if (isReference && !currentEqualsNew) instanceData.rememberReferenceValue(fieldName, currentValue);
|
|
68
68
|
if (flushedData) {
|
package/build/fields.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fields.cjs","names":["getEmInternalApi","getInstanceData","fail","getMetadata","cleanStringValue","isEntity","maybeResolveReferenceToId","maybeRequireTemporal"],"sources":["../src/fields.ts"],"sourcesContent":["import { getInstanceData } from \"src/BaseEntity.ts\";\nimport { type Entity, isEntity } from \"src/Entity.ts\";\nimport { getEmInternalApi } from \"src/EntityManager.ts\";\nimport { type Field, getMetadata } from \"src/EntityMetadata.ts\";\nimport { cleanStringValue, ensureNotDeleted, maybeResolveReferenceToId } from \"src/index.ts\";\nimport { maybeRequireTemporal } from \"src/serde/temporal.ts\";\nimport { fail } from \"src/utils.ts\";\n\n/**\n * Returns the current value of `fieldName`, marking the entity for read locking if the field is configured.\n *\n * Generated getters and direct application calls are protected by default. Only ORM\n * housekeeping should pass `isInternal`, not domain logic in hooks, rules, or reactions.\n * The field name is a string because generated getters and ORM internals know its type.\n */\nexport function getField(entity: Entity, fieldName: string, isInternal = false): any {\n const { em } = entity;\n const api = getEmInternalApi(em);\n api.pluginManager.beforeGetField(entity, fieldName);\n // We may not have converted the database column value into domain values yet\n const instanceData = getInstanceData(entity);\n const field = instanceData.metadata.allFields[fieldName];\n if (!isInternal && field?.assertUnchanged) em.assertUnchanged(entity);\n\n const { data } = instanceData;\n if (fieldName in data) {\n return data[fieldName];\n } else {\n if (!entity.isNewEntity) {\n const serde = field?.serde ?? fail(`Missing serde for ${fieldName}`);\n data[fieldName] = serde.fromRow(instanceData.rowData, instanceData.rowIndex);\n }\n return data[fieldName];\n }\n}\n\n/** Returns whether `fieldName` is a field/column on `entity` that can be changed. */\nexport function isChangeableField(entity: Entity, fieldName: string): boolean {\n return !!getMetadata(entity).allFields[fieldName]?.serde;\n}\n\n/** Returns whether `fieldName` has been set, even if it's undefined, on `entity`. */\nexport function isFieldSet(entity: Entity, fieldName: string): boolean {\n const { data } = getInstanceData(entity);\n if (fieldName in data) return true;\n // Avoid calling `getField` on new entities because it populates the field as a side effect.\n if (entity.isNewEntity) return false;\n // We may not have converted the database column value into domain values yet.\n getField(entity, fieldName, true);\n return fieldName in data;\n}\n\n/**\n * Sets the current value of `fieldName`, this is an internal method that should\n * only be called by the code-generated setters.\n *\n * We skip any typing like `fieldName: keyof T` because this method should only be\n * called by trusted codegen anyway.\n *\n * Returns `true` if the value was changed, or `false` if it was a noop.\n */\nexport function setField(entity: Entity, fieldName: string, newValue: any): boolean {\n ensureNotDeleted(entity, \"pending\");\n const { em } = entity;\n const api = getEmInternalApi(em);\n const { rm, pluginManager, indexManager, fieldLogger, isLoadedCache } = api;\n\n // If we're flushing, only allow writes from hooks\n api.checkWritesAllowed();\n\n // Tell any `#isLoaded` or `#value` caches that they might be stale\n isLoadedCache.resetIsLoaded(entity, fieldName);\n\n const instanceData = getInstanceData(entity);\n const { data, originalData, flushedData } = instanceData;\n\n // Do string sanitization\n const field = getMetadata(entity).allFields[fieldName];\n if (field.kind === \"primitive\" && field.type === \"string\" && field.sanitize !== false) {\n newValue = cleanStringValue(newValue);\n }\n\n const currentValue = getField(entity, fieldName, true);\n const isReference = field.kind === \"m2o\" || field.kind === \"poly\";\n const currentEqualsNew = fieldValueEquals(field, currentValue, newValue);\n // Check `config.setTransitions` tables while the change is still synchronous, before we change anything,\n // and record the transition for `em.flush` to run its (async) guards and reactions\n if (field.kind === \"enum\" && !currentEqualsNew) rm.queueTransition(entity, fieldName, currentValue, newValue);\n // Remember every prior reference value so `followReverseHint` can rewalk both original and transient owners.\n if (isReference && !currentEqualsNew) {\n instanceData.rememberReferenceValue(fieldName, currentValue);\n }\n\n // If a `set` occurs during the AsyncReactiveField-loop, copy the last-flushed value to flushedData.\n // Then our `pendingOperation` logic can tell \"do we need another micro-flush?\" separately\n // from our public-facing changed fields logic.\n if (flushedData) {\n if (fieldName in flushedData) {\n // We've already copied the last-micro-flush value into flushedData,\n // are we changing back to that? If so, we won't need another micro-flush.\n if (equalOrSameEntity(flushedData[fieldName], newValue)) {\n delete flushedData[fieldName];\n } else {\n // Otherwise just let data[fieldName] get the even-newer value, and keep\n // flushedData[fieldName] as the last-micro-flushed value.\n }\n } else if (!currentEqualsNew) {\n // This is the 1st rqf-loop change for this field, so let data[fieldName]\n // get the even newer value, but keep the last-micro-flushed value in flushedData.\n flushedData[fieldName] = currentValue;\n }\n }\n\n // \"Un-dirty\" our originalData if newValue is reverting to originalData\n if (fieldName in originalData) {\n if (fieldValueEquals(field, originalData[fieldName], newValue)) {\n indexManager.maybeUpdateFieldIndex(entity, fieldName, currentValue, newValue);\n\n pluginManager.beforeSetField(entity, fieldName, newValue);\n data[fieldName] = newValue;\n instanceData.markFieldClean(fieldName);\n\n fieldLogger.logSet(entity, fieldName, newValue);\n if (isReference && instanceData.getReferenceHistory(fieldName).length > 0) {\n rm.queueDownstreamReactables(entity, fieldName);\n } else {\n // For normal reverts, the field is clean again, so any pending downstream work can be removed.\n rm.dequeueDownstreamReactables(entity, fieldName);\n }\n\n return true;\n }\n }\n\n // Push this logic into a field serde type abstraction?\n if (currentEqualsNew) {\n // If we're doing `entity.field = undefined`, we'll be equal, but mark ourselves as \"set\"\n // so that defaults know to respect the intent of keeping this field undefined.\n if (!(fieldName in data)) data[fieldName] = undefined;\n return false;\n }\n\n // Only save the currentValue on the 1st change of this field\n if (!(fieldName in originalData)) {\n instanceData.markFieldDirty(fieldName, currentValue);\n }\n fieldLogger.logSet(entity, fieldName, newValue);\n rm.queueDownstreamReactables(entity, fieldName);\n\n indexManager.maybeUpdateFieldIndex(entity, fieldName, currentValue, newValue);\n\n pluginManager.beforeSetField(entity, fieldName, newValue);\n data[fieldName] = newValue;\n return true;\n}\n\nfunction fieldValueEquals(field: Field, a: unknown, b: unknown): boolean {\n // Primitive scalar sets are the common path, so avoid Date/Temporal/entity checks when both values cannot need them.\n if (field.kind !== \"m2o\" && field.kind !== \"poly\" && isNonObjectValue(a) && isNonObjectValue(b)) return a === b;\n return equalOrSameEntity(a, b);\n}\n\nfunction isNonObjectValue(value: unknown): boolean {\n return value === null || typeof value !== \"object\";\n}\n\nfunction equalOrSameEntity(a: any, b: any): boolean {\n return (\n equal(a, b) ||\n (Array.isArray(a) && Array.isArray(b) && equalArrays(a, b)) ||\n // This is kind of gross, but make sure not to compare two both-new entities\n (((isEntity(a) && !a.isNewEntity) || (isEntity(b) && !b.isNewEntity)) &&\n maybeResolveReferenceToId(a) === maybeResolveReferenceToId(b))\n );\n}\n\nexport function equalArrays(a: any[], b: any[]): boolean {\n if (a.length !== b.length) return false;\n return a.every((_: any, i) => equal(a[i], b[i]));\n}\n\nconst Temporal = maybeRequireTemporal()?.Temporal;\nexport function equal(a: any, b: any): boolean {\n if (a === b) return true;\n if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();\n if (Temporal) {\n if (a instanceof Temporal.ZonedDateTime && b instanceof Temporal.ZonedDateTime) return a.equals(b);\n if (a instanceof Temporal.PlainDateTime && b instanceof Temporal.PlainDateTime) return a.equals(b);\n if (a instanceof Temporal.PlainDate && b instanceof Temporal.PlainDate) return a.equals(b);\n }\n return false;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"fields.cjs","names":["getEmInternalApi","getInstanceData","fail","getMetadata","cleanStringValue","isEntity","maybeResolveReferenceToId","maybeRequireTemporal"],"sources":["../src/fields.ts"],"sourcesContent":["import { getInstanceData } from \"src/BaseEntity.ts\";\nimport { type Entity, isEntity } from \"src/Entity.ts\";\nimport { getEmInternalApi } from \"src/EntityManager.ts\";\nimport { type Field, getMetadata } from \"src/EntityMetadata.ts\";\nimport { cleanStringValue, ensureNotDeleted, maybeResolveReferenceToId } from \"src/index.ts\";\nimport { maybeRequireTemporal } from \"src/serde/temporal.ts\";\nimport { fail } from \"src/utils.ts\";\n\n/**\n * Returns the current value of `fieldName`, marking the entity for read locking if the field is configured.\n *\n * Generated getters and direct application calls are protected by default. Only ORM\n * housekeeping should pass `isInternal`, not domain logic in hooks, rules, or reactions.\n * The field name is a string because generated getters and ORM internals know its type.\n */\nexport function getField(entity: Entity, fieldName: string, isInternal = false): any {\n const { em } = entity;\n const api = getEmInternalApi(em);\n api.pluginManager.beforeGetField(entity, fieldName);\n // We may not have converted the database column value into domain values yet\n const instanceData = getInstanceData(entity);\n const field = instanceData.metadata.allFields[fieldName];\n if (!isInternal && field?.assertUnchanged) em.assertUnchanged(entity);\n\n const { data } = instanceData;\n if (fieldName in data) {\n return data[fieldName];\n } else {\n if (!entity.isNewEntity) {\n const serde = field?.serde ?? fail(`Missing serde for ${fieldName}`);\n data[fieldName] = serde.fromRow(instanceData.rowData, instanceData.rowIndex);\n }\n return data[fieldName];\n }\n}\n\n/** Returns whether `fieldName` is a field/column on `entity` that can be changed. */\nexport function isChangeableField(entity: Entity, fieldName: string): boolean {\n return !!getMetadata(entity).allFields[fieldName]?.serde;\n}\n\n/** Returns whether `fieldName` has been set, even if it's undefined, on `entity`. */\nexport function isFieldSet(entity: Entity, fieldName: string): boolean {\n const { data } = getInstanceData(entity);\n if (fieldName in data) return true;\n // Avoid calling `getField` on new entities because it populates the field as a side effect.\n if (entity.isNewEntity) return false;\n // We may not have converted the database column value into domain values yet.\n getField(entity, fieldName, true);\n return fieldName in data;\n}\n\n/**\n * Sets the current value of `fieldName`, this is an internal method that should\n * only be called by the code-generated setters.\n *\n * We skip any typing like `fieldName: keyof T` because this method should only be\n * called by trusted codegen anyway.\n *\n * Returns `true` if the value was changed, or `false` if it was a noop.\n */\nexport function setField(entity: Entity, fieldName: string, newValue: any): boolean {\n ensureNotDeleted(entity, \"pending\");\n const { em } = entity;\n const api = getEmInternalApi(em);\n const { rm, pluginManager, indexManager, fieldLogger, isLoadedCache } = api;\n\n // If we're flushing, only allow writes from hooks\n api.checkWritesAllowed();\n\n const instanceData = getInstanceData(entity);\n const { data, originalData, flushedData } = instanceData;\n\n // Do string sanitization\n const field = getMetadata(entity).allFields[fieldName];\n if (field.kind === \"primitive\" && field.type === \"string\" && field.sanitize !== false) {\n newValue = cleanStringValue(newValue);\n }\n\n const currentValue = getField(entity, fieldName, true);\n const isReference = field.kind === \"m2o\" || field.kind === \"poly\";\n const currentEqualsNew = fieldValueEquals(field, currentValue, newValue);\n\n // Tell any `#isLoaded` or `#value` caches that they might be stale, but only\n // after we've checked if the current value changed. Also we do it here,\n // before the \"reverting a dirty value to the original\" check below,\n // because caches might have already observed the dirty value, so would\n // still need to refresh.\n if (!currentEqualsNew) isLoadedCache.resetIsLoaded(entity, fieldName);\n\n // Check `config.setTransitions` tables while the change is still synchronous, before we change anything,\n // and record the transition for `em.flush` to run its (async) guards and reactions\n if (field.kind === \"enum\" && !currentEqualsNew) rm.queueTransition(entity, fieldName, currentValue, newValue);\n // Remember every prior reference value so `followReverseHint` can rewalk both original and transient owners.\n if (isReference && !currentEqualsNew) {\n instanceData.rememberReferenceValue(fieldName, currentValue);\n }\n\n // If a `set` occurs during the AsyncReactiveField-loop, copy the last-flushed value to flushedData.\n // Then our `pendingOperation` logic can tell \"do we need another micro-flush?\" separately\n // from our public-facing changed fields logic.\n if (flushedData) {\n if (fieldName in flushedData) {\n // We've already copied the last-micro-flush value into flushedData,\n // are we changing back to that? If so, we won't need another micro-flush.\n if (equalOrSameEntity(flushedData[fieldName], newValue)) {\n delete flushedData[fieldName];\n } else {\n // Otherwise just let data[fieldName] get the even-newer value, and keep\n // flushedData[fieldName] as the last-micro-flushed value.\n }\n } else if (!currentEqualsNew) {\n // This is the 1st rqf-loop change for this field, so let data[fieldName]\n // get the even newer value, but keep the last-micro-flushed value in flushedData.\n flushedData[fieldName] = currentValue;\n }\n }\n\n // \"Un-dirty\" our originalData if newValue is reverting to originalData\n if (fieldName in originalData) {\n if (fieldValueEquals(field, originalData[fieldName], newValue)) {\n indexManager.maybeUpdateFieldIndex(entity, fieldName, currentValue, newValue);\n\n pluginManager.beforeSetField(entity, fieldName, newValue);\n data[fieldName] = newValue;\n instanceData.markFieldClean(fieldName);\n\n fieldLogger.logSet(entity, fieldName, newValue);\n if (isReference && instanceData.getReferenceHistory(fieldName).length > 0) {\n rm.queueDownstreamReactables(entity, fieldName);\n } else {\n // For normal reverts, the field is clean again, so any pending downstream work can be removed.\n rm.dequeueDownstreamReactables(entity, fieldName);\n }\n\n return true;\n }\n }\n\n // Push this logic into a field serde type abstraction?\n if (currentEqualsNew) {\n // If we're doing `entity.field = undefined`, we'll be equal, but mark ourselves as \"set\"\n // so that defaults know to respect the intent of keeping this field undefined.\n if (!(fieldName in data)) data[fieldName] = undefined;\n return false;\n }\n\n // Only save the currentValue on the 1st change of this field\n if (!(fieldName in originalData)) {\n instanceData.markFieldDirty(fieldName, currentValue);\n }\n fieldLogger.logSet(entity, fieldName, newValue);\n rm.queueDownstreamReactables(entity, fieldName);\n\n indexManager.maybeUpdateFieldIndex(entity, fieldName, currentValue, newValue);\n\n pluginManager.beforeSetField(entity, fieldName, newValue);\n data[fieldName] = newValue;\n return true;\n}\n\nfunction fieldValueEquals(field: Field, a: unknown, b: unknown): boolean {\n // Primitive scalar sets are the common path, so avoid Date/Temporal/entity checks when both values cannot need them.\n if (field.kind !== \"m2o\" && field.kind !== \"poly\" && isNonObjectValue(a) && isNonObjectValue(b)) return a === b;\n return equalOrSameEntity(a, b);\n}\n\nfunction isNonObjectValue(value: unknown): boolean {\n return value === null || typeof value !== \"object\";\n}\n\nfunction equalOrSameEntity(a: any, b: any): boolean {\n return (\n equal(a, b) ||\n (Array.isArray(a) && Array.isArray(b) && equalArrays(a, b)) ||\n // This is kind of gross, but make sure not to compare two both-new entities\n (((isEntity(a) && !a.isNewEntity) || (isEntity(b) && !b.isNewEntity)) &&\n maybeResolveReferenceToId(a) === maybeResolveReferenceToId(b))\n );\n}\n\nexport function equalArrays(a: any[], b: any[]): boolean {\n if (a.length !== b.length) return false;\n return a.every((_: any, i) => equal(a[i], b[i]));\n}\n\nconst Temporal = maybeRequireTemporal()?.Temporal;\nexport function equal(a: any, b: any): boolean {\n if (a === b) return true;\n if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();\n if (Temporal) {\n if (a instanceof Temporal.ZonedDateTime && b instanceof Temporal.ZonedDateTime) return a.equals(b);\n if (a instanceof Temporal.PlainDateTime && b instanceof Temporal.PlainDateTime) return a.equals(b);\n if (a instanceof Temporal.PlainDate && b instanceof Temporal.PlainDate) return a.equals(b);\n }\n return false;\n}\n"],"mappings":"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"}
|
package/build/fields.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fields.d.cts","names":[],"sources":["../src/fields.ts"],"mappings":";;;;;;;;;wBAegB,SAAS,QAAQ,QAAQ,mBAAmB;;wBAsB5C,kBAAkB,QAAQ,QAAQ;;wBAKlC,WAAW,QAAQ,QAAQ;;;;;;;;;;wBAmB3B,SAAS,QAAQ,QAAQ,mBAAmB;
|
|
1
|
+
{"version":3,"file":"fields.d.cts","names":[],"sources":["../src/fields.ts"],"mappings":";;;;;;;;;wBAegB,SAAS,QAAQ,QAAQ,mBAAmB;;wBAsB5C,kBAAkB,QAAQ,QAAQ;;wBAKlC,WAAW,QAAQ,QAAQ;;;;;;;;;;wBAmB3B,SAAS,QAAQ,QAAQ,mBAAmB;wBAwH5C,YAAY,UAAU;wBAMtB,MAAM,QAAQ"}
|
package/build/fields.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fields.d.mts","names":[],"sources":["../src/fields.ts"],"mappings":";;;;;;;;;wBAegB,SAAS,QAAQ,QAAQ,mBAAmB;;wBAsB5C,kBAAkB,QAAQ,QAAQ;;wBAKlC,WAAW,QAAQ,QAAQ;;;;;;;;;;wBAmB3B,SAAS,QAAQ,QAAQ,mBAAmB;
|
|
1
|
+
{"version":3,"file":"fields.d.mts","names":[],"sources":["../src/fields.ts"],"mappings":";;;;;;;;;wBAegB,SAAS,QAAQ,QAAQ,mBAAmB;;wBAsB5C,kBAAkB,QAAQ,QAAQ;;wBAKlC,WAAW,QAAQ,QAAQ;;;;;;;;;;wBAmB3B,SAAS,QAAQ,QAAQ,mBAAmB;wBAwH5C,YAAY,UAAU;wBAMtB,MAAM,QAAQ"}
|
package/build/fields.js
CHANGED
|
@@ -54,7 +54,6 @@ function setField(entity, fieldName, newValue) {
|
|
|
54
54
|
const api = getEmInternalApi(em);
|
|
55
55
|
const { rm, pluginManager, indexManager, fieldLogger, isLoadedCache } = api;
|
|
56
56
|
api.checkWritesAllowed();
|
|
57
|
-
isLoadedCache.resetIsLoaded(entity, fieldName);
|
|
58
57
|
const instanceData = getInstanceData(entity);
|
|
59
58
|
const { data, originalData, flushedData } = instanceData;
|
|
60
59
|
const field = getMetadata(entity).allFields[fieldName];
|
|
@@ -62,6 +61,7 @@ function setField(entity, fieldName, newValue) {
|
|
|
62
61
|
const currentValue = getField(entity, fieldName, true);
|
|
63
62
|
const isReference = field.kind === "m2o" || field.kind === "poly";
|
|
64
63
|
const currentEqualsNew = fieldValueEquals(field, currentValue, newValue);
|
|
64
|
+
if (!currentEqualsNew) isLoadedCache.resetIsLoaded(entity, fieldName);
|
|
65
65
|
if (field.kind === "enum" && !currentEqualsNew) rm.queueTransition(entity, fieldName, currentValue, newValue);
|
|
66
66
|
if (isReference && !currentEqualsNew) instanceData.rememberReferenceValue(fieldName, currentValue);
|
|
67
67
|
if (flushedData) {
|
package/build/fields.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fields.js","names":[],"sources":["../src/fields.ts"],"sourcesContent":["import { getInstanceData } from \"src/BaseEntity.ts\";\nimport { type Entity, isEntity } from \"src/Entity.ts\";\nimport { getEmInternalApi } from \"src/EntityManager.ts\";\nimport { type Field, getMetadata } from \"src/EntityMetadata.ts\";\nimport { cleanStringValue, ensureNotDeleted, maybeResolveReferenceToId } from \"src/index.ts\";\nimport { maybeRequireTemporal } from \"src/serde/temporal.ts\";\nimport { fail } from \"src/utils.ts\";\n\n/**\n * Returns the current value of `fieldName`, marking the entity for read locking if the field is configured.\n *\n * Generated getters and direct application calls are protected by default. Only ORM\n * housekeeping should pass `isInternal`, not domain logic in hooks, rules, or reactions.\n * The field name is a string because generated getters and ORM internals know its type.\n */\nexport function getField(entity: Entity, fieldName: string, isInternal = false): any {\n const { em } = entity;\n const api = getEmInternalApi(em);\n api.pluginManager.beforeGetField(entity, fieldName);\n // We may not have converted the database column value into domain values yet\n const instanceData = getInstanceData(entity);\n const field = instanceData.metadata.allFields[fieldName];\n if (!isInternal && field?.assertUnchanged) em.assertUnchanged(entity);\n\n const { data } = instanceData;\n if (fieldName in data) {\n return data[fieldName];\n } else {\n if (!entity.isNewEntity) {\n const serde = field?.serde ?? fail(`Missing serde for ${fieldName}`);\n data[fieldName] = serde.fromRow(instanceData.rowData, instanceData.rowIndex);\n }\n return data[fieldName];\n }\n}\n\n/** Returns whether `fieldName` is a field/column on `entity` that can be changed. */\nexport function isChangeableField(entity: Entity, fieldName: string): boolean {\n return !!getMetadata(entity).allFields[fieldName]?.serde;\n}\n\n/** Returns whether `fieldName` has been set, even if it's undefined, on `entity`. */\nexport function isFieldSet(entity: Entity, fieldName: string): boolean {\n const { data } = getInstanceData(entity);\n if (fieldName in data) return true;\n // Avoid calling `getField` on new entities because it populates the field as a side effect.\n if (entity.isNewEntity) return false;\n // We may not have converted the database column value into domain values yet.\n getField(entity, fieldName, true);\n return fieldName in data;\n}\n\n/**\n * Sets the current value of `fieldName`, this is an internal method that should\n * only be called by the code-generated setters.\n *\n * We skip any typing like `fieldName: keyof T` because this method should only be\n * called by trusted codegen anyway.\n *\n * Returns `true` if the value was changed, or `false` if it was a noop.\n */\nexport function setField(entity: Entity, fieldName: string, newValue: any): boolean {\n ensureNotDeleted(entity, \"pending\");\n const { em } = entity;\n const api = getEmInternalApi(em);\n const { rm, pluginManager, indexManager, fieldLogger, isLoadedCache } = api;\n\n // If we're flushing, only allow writes from hooks\n api.checkWritesAllowed();\n\n // Tell any `#isLoaded` or `#value` caches that they might be stale\n isLoadedCache.resetIsLoaded(entity, fieldName);\n\n const instanceData = getInstanceData(entity);\n const { data, originalData, flushedData } = instanceData;\n\n // Do string sanitization\n const field = getMetadata(entity).allFields[fieldName];\n if (field.kind === \"primitive\" && field.type === \"string\" && field.sanitize !== false) {\n newValue = cleanStringValue(newValue);\n }\n\n const currentValue = getField(entity, fieldName, true);\n const isReference = field.kind === \"m2o\" || field.kind === \"poly\";\n const currentEqualsNew = fieldValueEquals(field, currentValue, newValue);\n // Check `config.setTransitions` tables while the change is still synchronous, before we change anything,\n // and record the transition for `em.flush` to run its (async) guards and reactions\n if (field.kind === \"enum\" && !currentEqualsNew) rm.queueTransition(entity, fieldName, currentValue, newValue);\n // Remember every prior reference value so `followReverseHint` can rewalk both original and transient owners.\n if (isReference && !currentEqualsNew) {\n instanceData.rememberReferenceValue(fieldName, currentValue);\n }\n\n // If a `set` occurs during the AsyncReactiveField-loop, copy the last-flushed value to flushedData.\n // Then our `pendingOperation` logic can tell \"do we need another micro-flush?\" separately\n // from our public-facing changed fields logic.\n if (flushedData) {\n if (fieldName in flushedData) {\n // We've already copied the last-micro-flush value into flushedData,\n // are we changing back to that? If so, we won't need another micro-flush.\n if (equalOrSameEntity(flushedData[fieldName], newValue)) {\n delete flushedData[fieldName];\n } else {\n // Otherwise just let data[fieldName] get the even-newer value, and keep\n // flushedData[fieldName] as the last-micro-flushed value.\n }\n } else if (!currentEqualsNew) {\n // This is the 1st rqf-loop change for this field, so let data[fieldName]\n // get the even newer value, but keep the last-micro-flushed value in flushedData.\n flushedData[fieldName] = currentValue;\n }\n }\n\n // \"Un-dirty\" our originalData if newValue is reverting to originalData\n if (fieldName in originalData) {\n if (fieldValueEquals(field, originalData[fieldName], newValue)) {\n indexManager.maybeUpdateFieldIndex(entity, fieldName, currentValue, newValue);\n\n pluginManager.beforeSetField(entity, fieldName, newValue);\n data[fieldName] = newValue;\n instanceData.markFieldClean(fieldName);\n\n fieldLogger.logSet(entity, fieldName, newValue);\n if (isReference && instanceData.getReferenceHistory(fieldName).length > 0) {\n rm.queueDownstreamReactables(entity, fieldName);\n } else {\n // For normal reverts, the field is clean again, so any pending downstream work can be removed.\n rm.dequeueDownstreamReactables(entity, fieldName);\n }\n\n return true;\n }\n }\n\n // Push this logic into a field serde type abstraction?\n if (currentEqualsNew) {\n // If we're doing `entity.field = undefined`, we'll be equal, but mark ourselves as \"set\"\n // so that defaults know to respect the intent of keeping this field undefined.\n if (!(fieldName in data)) data[fieldName] = undefined;\n return false;\n }\n\n // Only save the currentValue on the 1st change of this field\n if (!(fieldName in originalData)) {\n instanceData.markFieldDirty(fieldName, currentValue);\n }\n fieldLogger.logSet(entity, fieldName, newValue);\n rm.queueDownstreamReactables(entity, fieldName);\n\n indexManager.maybeUpdateFieldIndex(entity, fieldName, currentValue, newValue);\n\n pluginManager.beforeSetField(entity, fieldName, newValue);\n data[fieldName] = newValue;\n return true;\n}\n\nfunction fieldValueEquals(field: Field, a: unknown, b: unknown): boolean {\n // Primitive scalar sets are the common path, so avoid Date/Temporal/entity checks when both values cannot need them.\n if (field.kind !== \"m2o\" && field.kind !== \"poly\" && isNonObjectValue(a) && isNonObjectValue(b)) return a === b;\n return equalOrSameEntity(a, b);\n}\n\nfunction isNonObjectValue(value: unknown): boolean {\n return value === null || typeof value !== \"object\";\n}\n\nfunction equalOrSameEntity(a: any, b: any): boolean {\n return (\n equal(a, b) ||\n (Array.isArray(a) && Array.isArray(b) && equalArrays(a, b)) ||\n // This is kind of gross, but make sure not to compare two both-new entities\n (((isEntity(a) && !a.isNewEntity) || (isEntity(b) && !b.isNewEntity)) &&\n maybeResolveReferenceToId(a) === maybeResolveReferenceToId(b))\n );\n}\n\nexport function equalArrays(a: any[], b: any[]): boolean {\n if (a.length !== b.length) return false;\n return a.every((_: any, i) => equal(a[i], b[i]));\n}\n\nconst Temporal = maybeRequireTemporal()?.Temporal;\nexport function equal(a: any, b: any): boolean {\n if (a === b) return true;\n if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();\n if (Temporal) {\n if (a instanceof Temporal.ZonedDateTime && b instanceof Temporal.ZonedDateTime) return a.equals(b);\n if (a instanceof Temporal.PlainDateTime && b instanceof Temporal.PlainDateTime) return a.equals(b);\n if (a instanceof Temporal.PlainDate && b instanceof Temporal.PlainDate) return a.equals(b);\n }\n return false;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"fields.js","names":[],"sources":["../src/fields.ts"],"sourcesContent":["import { getInstanceData } from \"src/BaseEntity.ts\";\nimport { type Entity, isEntity } from \"src/Entity.ts\";\nimport { getEmInternalApi } from \"src/EntityManager.ts\";\nimport { type Field, getMetadata } from \"src/EntityMetadata.ts\";\nimport { cleanStringValue, ensureNotDeleted, maybeResolveReferenceToId } from \"src/index.ts\";\nimport { maybeRequireTemporal } from \"src/serde/temporal.ts\";\nimport { fail } from \"src/utils.ts\";\n\n/**\n * Returns the current value of `fieldName`, marking the entity for read locking if the field is configured.\n *\n * Generated getters and direct application calls are protected by default. Only ORM\n * housekeeping should pass `isInternal`, not domain logic in hooks, rules, or reactions.\n * The field name is a string because generated getters and ORM internals know its type.\n */\nexport function getField(entity: Entity, fieldName: string, isInternal = false): any {\n const { em } = entity;\n const api = getEmInternalApi(em);\n api.pluginManager.beforeGetField(entity, fieldName);\n // We may not have converted the database column value into domain values yet\n const instanceData = getInstanceData(entity);\n const field = instanceData.metadata.allFields[fieldName];\n if (!isInternal && field?.assertUnchanged) em.assertUnchanged(entity);\n\n const { data } = instanceData;\n if (fieldName in data) {\n return data[fieldName];\n } else {\n if (!entity.isNewEntity) {\n const serde = field?.serde ?? fail(`Missing serde for ${fieldName}`);\n data[fieldName] = serde.fromRow(instanceData.rowData, instanceData.rowIndex);\n }\n return data[fieldName];\n }\n}\n\n/** Returns whether `fieldName` is a field/column on `entity` that can be changed. */\nexport function isChangeableField(entity: Entity, fieldName: string): boolean {\n return !!getMetadata(entity).allFields[fieldName]?.serde;\n}\n\n/** Returns whether `fieldName` has been set, even if it's undefined, on `entity`. */\nexport function isFieldSet(entity: Entity, fieldName: string): boolean {\n const { data } = getInstanceData(entity);\n if (fieldName in data) return true;\n // Avoid calling `getField` on new entities because it populates the field as a side effect.\n if (entity.isNewEntity) return false;\n // We may not have converted the database column value into domain values yet.\n getField(entity, fieldName, true);\n return fieldName in data;\n}\n\n/**\n * Sets the current value of `fieldName`, this is an internal method that should\n * only be called by the code-generated setters.\n *\n * We skip any typing like `fieldName: keyof T` because this method should only be\n * called by trusted codegen anyway.\n *\n * Returns `true` if the value was changed, or `false` if it was a noop.\n */\nexport function setField(entity: Entity, fieldName: string, newValue: any): boolean {\n ensureNotDeleted(entity, \"pending\");\n const { em } = entity;\n const api = getEmInternalApi(em);\n const { rm, pluginManager, indexManager, fieldLogger, isLoadedCache } = api;\n\n // If we're flushing, only allow writes from hooks\n api.checkWritesAllowed();\n\n const instanceData = getInstanceData(entity);\n const { data, originalData, flushedData } = instanceData;\n\n // Do string sanitization\n const field = getMetadata(entity).allFields[fieldName];\n if (field.kind === \"primitive\" && field.type === \"string\" && field.sanitize !== false) {\n newValue = cleanStringValue(newValue);\n }\n\n const currentValue = getField(entity, fieldName, true);\n const isReference = field.kind === \"m2o\" || field.kind === \"poly\";\n const currentEqualsNew = fieldValueEquals(field, currentValue, newValue);\n\n // Tell any `#isLoaded` or `#value` caches that they might be stale, but only\n // after we've checked if the current value changed. Also we do it here,\n // before the \"reverting a dirty value to the original\" check below,\n // because caches might have already observed the dirty value, so would\n // still need to refresh.\n if (!currentEqualsNew) isLoadedCache.resetIsLoaded(entity, fieldName);\n\n // Check `config.setTransitions` tables while the change is still synchronous, before we change anything,\n // and record the transition for `em.flush` to run its (async) guards and reactions\n if (field.kind === \"enum\" && !currentEqualsNew) rm.queueTransition(entity, fieldName, currentValue, newValue);\n // Remember every prior reference value so `followReverseHint` can rewalk both original and transient owners.\n if (isReference && !currentEqualsNew) {\n instanceData.rememberReferenceValue(fieldName, currentValue);\n }\n\n // If a `set` occurs during the AsyncReactiveField-loop, copy the last-flushed value to flushedData.\n // Then our `pendingOperation` logic can tell \"do we need another micro-flush?\" separately\n // from our public-facing changed fields logic.\n if (flushedData) {\n if (fieldName in flushedData) {\n // We've already copied the last-micro-flush value into flushedData,\n // are we changing back to that? If so, we won't need another micro-flush.\n if (equalOrSameEntity(flushedData[fieldName], newValue)) {\n delete flushedData[fieldName];\n } else {\n // Otherwise just let data[fieldName] get the even-newer value, and keep\n // flushedData[fieldName] as the last-micro-flushed value.\n }\n } else if (!currentEqualsNew) {\n // This is the 1st rqf-loop change for this field, so let data[fieldName]\n // get the even newer value, but keep the last-micro-flushed value in flushedData.\n flushedData[fieldName] = currentValue;\n }\n }\n\n // \"Un-dirty\" our originalData if newValue is reverting to originalData\n if (fieldName in originalData) {\n if (fieldValueEquals(field, originalData[fieldName], newValue)) {\n indexManager.maybeUpdateFieldIndex(entity, fieldName, currentValue, newValue);\n\n pluginManager.beforeSetField(entity, fieldName, newValue);\n data[fieldName] = newValue;\n instanceData.markFieldClean(fieldName);\n\n fieldLogger.logSet(entity, fieldName, newValue);\n if (isReference && instanceData.getReferenceHistory(fieldName).length > 0) {\n rm.queueDownstreamReactables(entity, fieldName);\n } else {\n // For normal reverts, the field is clean again, so any pending downstream work can be removed.\n rm.dequeueDownstreamReactables(entity, fieldName);\n }\n\n return true;\n }\n }\n\n // Push this logic into a field serde type abstraction?\n if (currentEqualsNew) {\n // If we're doing `entity.field = undefined`, we'll be equal, but mark ourselves as \"set\"\n // so that defaults know to respect the intent of keeping this field undefined.\n if (!(fieldName in data)) data[fieldName] = undefined;\n return false;\n }\n\n // Only save the currentValue on the 1st change of this field\n if (!(fieldName in originalData)) {\n instanceData.markFieldDirty(fieldName, currentValue);\n }\n fieldLogger.logSet(entity, fieldName, newValue);\n rm.queueDownstreamReactables(entity, fieldName);\n\n indexManager.maybeUpdateFieldIndex(entity, fieldName, currentValue, newValue);\n\n pluginManager.beforeSetField(entity, fieldName, newValue);\n data[fieldName] = newValue;\n return true;\n}\n\nfunction fieldValueEquals(field: Field, a: unknown, b: unknown): boolean {\n // Primitive scalar sets are the common path, so avoid Date/Temporal/entity checks when both values cannot need them.\n if (field.kind !== \"m2o\" && field.kind !== \"poly\" && isNonObjectValue(a) && isNonObjectValue(b)) return a === b;\n return equalOrSameEntity(a, b);\n}\n\nfunction isNonObjectValue(value: unknown): boolean {\n return value === null || typeof value !== \"object\";\n}\n\nfunction equalOrSameEntity(a: any, b: any): boolean {\n return (\n equal(a, b) ||\n (Array.isArray(a) && Array.isArray(b) && equalArrays(a, b)) ||\n // This is kind of gross, but make sure not to compare two both-new entities\n (((isEntity(a) && !a.isNewEntity) || (isEntity(b) && !b.isNewEntity)) &&\n maybeResolveReferenceToId(a) === maybeResolveReferenceToId(b))\n );\n}\n\nexport function equalArrays(a: any[], b: any[]): boolean {\n if (a.length !== b.length) return false;\n return a.every((_: any, i) => equal(a[i], b[i]));\n}\n\nconst Temporal = maybeRequireTemporal()?.Temporal;\nexport function equal(a: any, b: any): boolean {\n if (a === b) return true;\n if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();\n if (Temporal) {\n if (a instanceof Temporal.ZonedDateTime && b instanceof Temporal.ZonedDateTime) return a.equals(b);\n if (a instanceof Temporal.PlainDateTime && b instanceof Temporal.PlainDateTime) return a.equals(b);\n if (a instanceof Temporal.PlainDate && b instanceof Temporal.PlainDate) return a.equals(b);\n }\n return false;\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "joist-core",
|
|
3
|
-
"version": "2.4.0-next.
|
|
3
|
+
"version": "2.4.0-next.56",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"build"
|
|
43
43
|
],
|
|
44
44
|
"peerDependencies": {
|
|
45
|
-
"joist-utils": "2.4.0-next.
|
|
45
|
+
"joist-utils": "2.4.0-next.56"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"ansis": "^4.4.0",
|