joist-core 2.4.0-next.14 → 2.4.0-next.16

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.
@@ -72,6 +72,20 @@ var LazyFieldImpl = class extends require_relations_AbstractPropertyImpl.Abstrac
72
72
  if (require_BaseEntity.getInstanceData(this.entity).isOrWasNew) return true;
73
73
  return this.#loaded;
74
74
  }
75
+ /**
76
+ * Marks this field loaded, using its value in entity data or reading it from the entity's row.
77
+ *
78
+ * The caller must know the value is present in entity data or the row; default SELECTs omit lazy columns.
79
+ */
80
+ preload() {
81
+ const { data, rowData, rowIndex } = require_BaseEntity.getInstanceData(this.entity);
82
+ if (!(this.#fieldName in data)) {
83
+ const field = require_EntityMetadata.getMetadata(this.entity).allFields[this.#fieldName];
84
+ data[this.#fieldName] = field.serde.fromRow(rowData, rowIndex);
85
+ }
86
+ this.#loaded = true;
87
+ this.#loadPromise = void 0;
88
+ }
75
89
  /** Called after em.flush to invalidate the cached value, since the db state may have changed. */
76
90
  resetAfterFlush() {
77
91
  this.#loaded = false;
@@ -1 +1 @@
1
- {"version":3,"file":"LazyField.cjs","names":["lazyField","AbstractPropertyImpl","#fieldName","#loaded","#loadPromise","#value","getMetadata","getInstanceData","PropertyT","abbreviation","keyToNumber","kqDot","PojoRowData","tagId"],"sources":["../../src/relations/LazyField.ts"],"sourcesContent":["import { getInstanceData } from \"src/BaseEntity.ts\";\nimport { type BatchLoader } from \"src/batchloaders/BatchLoader.ts\";\nimport { type Entity } from \"src/Entity.ts\";\nimport { type EntityManager } from \"src/EntityManager.ts\";\nimport { type EntityMetadata, type PrimitiveField, getMetadata } from \"src/EntityMetadata.ts\";\nimport { setField } from \"src/fields.ts\";\nimport { keyToNumber, tagId } from \"src/keys.ts\";\nimport { lazyField } from \"src/newEntity.ts\";\nimport type { ParsedFindQuery } from \"src/queries/find/QueryParser.ts\";\nimport { kqDot } from \"src/queries/sql/keywords.ts\";\nimport { AbstractPropertyImpl } from \"src/relations/AbstractPropertyImpl.ts\";\nimport { type LoadedProperty, type Property } from \"src/relations/hasProperty.ts\";\nimport { PropertyT } from \"src/relations/PropertySymbols.ts\";\nimport { PojoRowData } from \"src/RowData.ts\";\nimport { abbreviation } from \"src/utils.ts\";\n\nexport const lazyColumnLoadOperation = \"lazy-column-load\";\n\n/**\n * The \"original value\" of an unloaded lazy column: unknown, and never equal to any set value.\n *\n * Seeded into `__orm.data` by `LazyFieldImpl.set` so that setting an unloaded column (even to\n * `undefined`) always registers as a change, i.e. instead of comparing equal to the `undefined`\n * that the excluded column would otherwise read as.\n */\nexport const notLoadedValue = Symbol(\"notLoadedValue\");\n\n/**\n * A single-column primitive (i.e. a large `jsonb`/`text`) that is deliberately excluded from the\n * entity's default SELECT and only fetched from the database on demand.\n *\n * I.e. `await author.someBigColumn.load()`, or synchronously via `.get` after `em.populate`.\n */\nexport interface LazyField<T extends Entity, V> extends Property<T, V> {\n load(opts?: { forceReload?: boolean }): Promise<V>;\n /** Sets the value in memory (dirtied for the next flush), i.e. without loading the current db value first. */\n set(value: V): void;\n}\n\n/** Creates a `LazyField` for a column that codegen has marked `lazy: true`. */\nexport function hasLazyField<T extends Entity, V>(): LazyField<T, V> {\n return lazyField((entity: T, fieldName: string) => new LazyFieldImpl<T, V>(entity, fieldName));\n}\n\nexport class LazyFieldImpl<T extends Entity, V> extends AbstractPropertyImpl<T> implements LazyField<T, V> {\n #fieldName: string;\n #loaded = false;\n #loadPromise: Promise<V> | undefined;\n\n constructor(entity: T, fieldName: string) {\n super(entity);\n this.#fieldName = fieldName;\n }\n\n /** Fetches this column from the database (batched across peers) and caches it on the entity. */\n load(opts?: { forceReload?: boolean }): Promise<V> {\n const { entity } = this;\n if (opts?.forceReload) {\n this.#loaded = false;\n this.#loadPromise = undefined;\n }\n if (this.isLoaded) return Promise.resolve(this.#value);\n const meta = getMetadata(entity);\n return (this.#loadPromise ??= lazyColumnBatchLoader(entity.em, meta, this.#fieldName)\n .load(entity.idTagged)\n .then(() => {\n this.#loaded = true;\n return this.#value;\n }));\n }\n\n get get(): V {\n if (!this.isLoaded) {\n throw new Error(`${this.#fieldName} has not been loaded yet, use '.load()' or an 'em.populate' hint`);\n }\n return this.#value;\n }\n\n /** Sets the value in memory; the column is dirtied and written on the next `em.flush`. */\n set(value: V): void {\n if (this.isLoaded) {\n setField(this.entity, this.#fieldName, value);\n } else {\n // Seed `notLoadedValue` as the current value so the set always registers as a change against\n // the unknown db value: an unloaded column otherwise reads as `undefined`, which would make\n // `set(undefined)` a silent no-op, and a later `set(undefined)` falsely revert to clean.\n const { data } = getInstanceData(this.entity);\n data[this.#fieldName] = notLoadedValue;\n try {\n setField(this.entity, this.#fieldName, value);\n } catch (e) {\n // Don't leak the marker as a readable value, i.e. if setting on a deleted entity throws\n delete data[this.#fieldName];\n throw e;\n }\n }\n this.#loaded = true;\n }\n\n get isLoaded(): boolean {\n // New entities keep their value in-memory, so there's nothing to lazy-load.\n if (getInstanceData(this.entity).isOrWasNew) return true;\n return this.#loaded;\n }\n\n /** Called after em.flush to invalidate the cached value, since the db state may have changed. */\n resetAfterFlush(): void {\n this.#loaded = false;\n this.#loadPromise = undefined;\n }\n\n /** The domain value, which `load`/`set` keep in the entity's `__orm.data` like any other column. */\n get #value(): V {\n return getInstanceData(this.entity).data[this.#fieldName] as V;\n }\n\n [PropertyT] = undefined as any as T;\n}\n\n/** Type guard utility for determining if an entity field is a LazyField. */\nexport function isLazyField(maybe: any): maybe is LazyField<any, any> {\n return maybe instanceof LazyFieldImpl;\n}\n\n/** Type guard utility for determining if an entity field is a loaded LazyField. */\nexport function isLoadedLazyField(maybe: any): maybe is LazyField<any, any> & LoadedProperty<any, any> {\n return isLazyField(maybe) && maybe.isLoaded;\n}\n\n/** Batches `LazyField.load`s for the same column into a single `SELECT id, <column> ... WHERE id IN (...)`. */\nfunction lazyColumnBatchLoader(em: EntityManager, meta: EntityMetadata, fieldName: string): BatchLoader<string> {\n return em.getBatchLoader(lazyColumnLoadOperation, `${meta.tableName}-${fieldName}`, async (taggedIds) => {\n const field = meta.allFields[fieldName] as PrimitiveField;\n const column = field.serde.columns[0];\n const alias = abbreviation(meta.tableName);\n const keys = taggedIds.map((id) => keyToNumber(meta, id));\n const query: ParsedFindQuery = {\n selects: [`${kqDot(alias, \"id\")} as id`, kqDot(alias, column.columnName)],\n tables: [{ alias, join: \"primary\", table: meta.tableName }],\n condition: {\n kind: \"exp\",\n op: \"and\",\n conditions: [{ kind: \"column\", alias, column: \"id\", dbType: meta.idDbType, cond: { kind: \"in\", value: keys } }],\n },\n orderBys: [],\n };\n const rows = await em[\"executeFind\"](meta, lazyColumnLoadOperation, query, {});\n const rowData = new PojoRowData(rows);\n for (let i = 0; i < rows.length; i++) {\n const entity = em.findExistingInstance(tagId(meta, rows[i].id));\n if (entity) getInstanceData(entity).data[fieldName] = field.serde.fromRow(rowData, i);\n }\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"LazyField.cjs","names":["lazyField","AbstractPropertyImpl","#fieldName","#loaded","#loadPromise","#value","getMetadata","getInstanceData","PropertyT","abbreviation","keyToNumber","kqDot","PojoRowData","tagId"],"sources":["../../src/relations/LazyField.ts"],"sourcesContent":["import { getInstanceData } from \"src/BaseEntity.ts\";\nimport { type BatchLoader } from \"src/batchloaders/BatchLoader.ts\";\nimport { type Entity } from \"src/Entity.ts\";\nimport { type EntityManager } from \"src/EntityManager.ts\";\nimport { type EntityMetadata, type PrimitiveField, getMetadata } from \"src/EntityMetadata.ts\";\nimport { setField } from \"src/fields.ts\";\nimport { keyToNumber, tagId } from \"src/keys.ts\";\nimport { lazyField } from \"src/newEntity.ts\";\nimport type { ParsedFindQuery } from \"src/queries/find/QueryParser.ts\";\nimport { kqDot } from \"src/queries/sql/keywords.ts\";\nimport { AbstractPropertyImpl } from \"src/relations/AbstractPropertyImpl.ts\";\nimport { type LoadedProperty, type Property } from \"src/relations/hasProperty.ts\";\nimport { PropertyT } from \"src/relations/PropertySymbols.ts\";\nimport { PojoRowData } from \"src/RowData.ts\";\nimport { abbreviation } from \"src/utils.ts\";\n\nexport const lazyColumnLoadOperation = \"lazy-column-load\";\n\n/**\n * The \"original value\" of an unloaded lazy column: unknown, and never equal to any set value.\n *\n * Seeded into `__orm.data` by `LazyFieldImpl.set` so that setting an unloaded column (even to\n * `undefined`) always registers as a change, i.e. instead of comparing equal to the `undefined`\n * that the excluded column would otherwise read as.\n */\nexport const notLoadedValue = Symbol(\"notLoadedValue\");\n\n/**\n * A single-column primitive (i.e. a large `jsonb`/`text`) that is deliberately excluded from the\n * entity's default SELECT and only fetched from the database on demand.\n *\n * I.e. `await author.someBigColumn.load()`, or synchronously via `.get` after `em.populate`.\n */\nexport interface LazyField<T extends Entity, V> extends Property<T, V> {\n load(opts?: { forceReload?: boolean }): Promise<V>;\n /** Sets the value in memory (dirtied for the next flush), i.e. without loading the current db value first. */\n set(value: V): void;\n}\n\n/** Creates a `LazyField` for a column that codegen has marked `lazy: true`. */\nexport function hasLazyField<T extends Entity, V>(): LazyField<T, V> {\n return lazyField((entity: T, fieldName: string) => new LazyFieldImpl<T, V>(entity, fieldName));\n}\n\nexport class LazyFieldImpl<T extends Entity, V> extends AbstractPropertyImpl<T> implements LazyField<T, V> {\n #fieldName: string;\n #loaded = false;\n #loadPromise: Promise<V> | undefined;\n\n constructor(entity: T, fieldName: string) {\n super(entity);\n this.#fieldName = fieldName;\n }\n\n /** Fetches this column from the database (batched across peers) and caches it on the entity. */\n load(opts?: { forceReload?: boolean }): Promise<V> {\n const { entity } = this;\n if (opts?.forceReload) {\n this.#loaded = false;\n this.#loadPromise = undefined;\n }\n if (this.isLoaded) return Promise.resolve(this.#value);\n const meta = getMetadata(entity);\n return (this.#loadPromise ??= lazyColumnBatchLoader(entity.em, meta, this.#fieldName)\n .load(entity.idTagged)\n .then(() => {\n this.#loaded = true;\n return this.#value;\n }));\n }\n\n get get(): V {\n if (!this.isLoaded) {\n throw new Error(`${this.#fieldName} has not been loaded yet, use '.load()' or an 'em.populate' hint`);\n }\n return this.#value;\n }\n\n /** Sets the value in memory; the column is dirtied and written on the next `em.flush`. */\n set(value: V): void {\n if (this.isLoaded) {\n setField(this.entity, this.#fieldName, value);\n } else {\n // Seed `notLoadedValue` as the current value so the set always registers as a change against\n // the unknown db value: an unloaded column otherwise reads as `undefined`, which would make\n // `set(undefined)` a silent no-op, and a later `set(undefined)` falsely revert to clean.\n const { data } = getInstanceData(this.entity);\n data[this.#fieldName] = notLoadedValue;\n try {\n setField(this.entity, this.#fieldName, value);\n } catch (e) {\n // Don't leak the marker as a readable value, i.e. if setting on a deleted entity throws\n delete data[this.#fieldName];\n throw e;\n }\n }\n this.#loaded = true;\n }\n\n get isLoaded(): boolean {\n // New entities keep their value in-memory, so there's nothing to lazy-load.\n if (getInstanceData(this.entity).isOrWasNew) return true;\n return this.#loaded;\n }\n\n /**\n * Marks this field loaded, using its value in entity data or reading it from the entity's row.\n *\n * The caller must know the value is present in entity data or the row; default SELECTs omit lazy columns.\n */\n preload(): void {\n const { data, rowData, rowIndex } = getInstanceData(this.entity);\n if (!(this.#fieldName in data)) {\n const field = getMetadata(this.entity).allFields[this.#fieldName];\n data[this.#fieldName] = field.serde!.fromRow(rowData, rowIndex);\n }\n\n this.#loaded = true;\n this.#loadPromise = undefined;\n }\n\n /** Called after em.flush to invalidate the cached value, since the db state may have changed. */\n resetAfterFlush(): void {\n this.#loaded = false;\n this.#loadPromise = undefined;\n }\n\n /** The domain value, which `load`/`set` keep in the entity's `__orm.data` like any other column. */\n get #value(): V {\n return getInstanceData(this.entity).data[this.#fieldName] as V;\n }\n\n [PropertyT] = undefined as any as T;\n}\n\n/** Type guard utility for determining if an entity field is a LazyField. */\nexport function isLazyField(maybe: any): maybe is LazyField<any, any> {\n return maybe instanceof LazyFieldImpl;\n}\n\n/** Type guard utility for determining if an entity field is a loaded LazyField. */\nexport function isLoadedLazyField(maybe: any): maybe is LazyField<any, any> & LoadedProperty<any, any> {\n return isLazyField(maybe) && maybe.isLoaded;\n}\n\n/** Batches `LazyField.load`s for the same column into a single `SELECT id, <column> ... WHERE id IN (...)`. */\nfunction lazyColumnBatchLoader(em: EntityManager, meta: EntityMetadata, fieldName: string): BatchLoader<string> {\n return em.getBatchLoader(lazyColumnLoadOperation, `${meta.tableName}-${fieldName}`, async (taggedIds) => {\n const field = meta.allFields[fieldName] as PrimitiveField;\n const column = field.serde.columns[0];\n const alias = abbreviation(meta.tableName);\n const keys = taggedIds.map((id) => keyToNumber(meta, id));\n const query: ParsedFindQuery = {\n selects: [`${kqDot(alias, \"id\")} as id`, kqDot(alias, column.columnName)],\n tables: [{ alias, join: \"primary\", table: meta.tableName }],\n condition: {\n kind: \"exp\",\n op: \"and\",\n conditions: [{ kind: \"column\", alias, column: \"id\", dbType: meta.idDbType, cond: { kind: \"in\", value: keys } }],\n },\n orderBys: [],\n };\n const rows = await em[\"executeFind\"](meta, lazyColumnLoadOperation, query, {});\n const rowData = new PojoRowData(rows);\n for (let i = 0; i < rows.length; i++) {\n const entity = em.findExistingInstance(tagId(meta, rows[i].id));\n if (entity) getInstanceData(entity).data[fieldName] = field.serde.fromRow(rowData, i);\n }\n });\n}\n"],"mappings":"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"}
@@ -38,6 +38,12 @@ declare class LazyFieldImpl<T extends Entity, V> extends AbstractPropertyImpl<T>
38
38
  /** Sets the value in memory; the column is dirtied and written on the next `em.flush`. */
39
39
  set(value: V): void;
40
40
  get isLoaded(): boolean;
41
+ /**
42
+ * Marks this field loaded, using its value in entity data or reading it from the entity's row.
43
+ *
44
+ * The caller must know the value is present in entity data or the row; default SELECTs omit lazy columns.
45
+ */
46
+ preload(): void;
41
47
  /** Called after em.flush to invalidate the cached value, since the db state may have changed. */
42
48
  resetAfterFlush(): void;
43
49
  [PropertyT]: T;
@@ -1 +1 @@
1
- {"version":3,"file":"LazyField.d.cts","names":[],"sources":["../../src/relations/LazyField.ts"],"mappings":";;;;;cAgBa;;;;;;;;cASA;;;;;;;UAQI,UAAU,UAAU,QAAQ,WAAW,SAAS,GAAG;EAClE,KAAK;IAAS;MAA0B,QAAQ;;EAEhD,IAAI,OAAO;;;iBAIG,aAAa,UAAU,QAAQ,MAAM,UAAU,GAAG;cAIrD,cAAc,UAAU,QAAQ,WAAW,qBAAqB,cAAc,UAAU,GAAG;;EAKtG,YAAY,QAAQ,GAAG;;EAMvB,KAAK;IAAS;MAA0B,QAAQ;MAgB5C,OAAO;;EAQX,IAAI,OAAO;MAoBP;;EAOJ;GAUC,YAAiC;;;iBAIpB,YAAY,aAAa,SAAS;;iBAKlC,kBAAkB,aAAa,SAAS,sBAAsB"}
1
+ {"version":3,"file":"LazyField.d.cts","names":[],"sources":["../../src/relations/LazyField.ts"],"mappings":";;;;;cAgBa;;;;;;;;cASA;;;;;;;UAQI,UAAU,UAAU,QAAQ,WAAW,SAAS,GAAG;EAClE,KAAK;IAAS;MAA0B,QAAQ;;EAEhD,IAAI,OAAO;;;iBAIG,aAAa,UAAU,QAAQ,MAAM,UAAU,GAAG;cAIrD,cAAc,UAAU,QAAQ,WAAW,qBAAqB,cAAc,UAAU,GAAG;;EAKtG,YAAY,QAAQ,GAAG;;EAMvB,KAAK;IAAS;MAA0B,QAAQ;MAgB5C,OAAO;;EAQX,IAAI,OAAO;MAoBP;;;;;;EAWJ;;EAYA;GAUC,YAAiC;;;iBAIpB,YAAY,aAAa,SAAS;;iBAKlC,kBAAkB,aAAa,SAAS,sBAAsB"}
@@ -38,6 +38,12 @@ declare class LazyFieldImpl<T extends Entity, V> extends AbstractPropertyImpl<T>
38
38
  /** Sets the value in memory; the column is dirtied and written on the next `em.flush`. */
39
39
  set(value: V): void;
40
40
  get isLoaded(): boolean;
41
+ /**
42
+ * Marks this field loaded, using its value in entity data or reading it from the entity's row.
43
+ *
44
+ * The caller must know the value is present in entity data or the row; default SELECTs omit lazy columns.
45
+ */
46
+ preload(): void;
41
47
  /** Called after em.flush to invalidate the cached value, since the db state may have changed. */
42
48
  resetAfterFlush(): void;
43
49
  [PropertyT]: T;
@@ -1 +1 @@
1
- {"version":3,"file":"LazyField.d.mts","names":[],"sources":["../../src/relations/LazyField.ts"],"mappings":";;;;;cAgBa;;;;;;;;cASA;;;;;;;UAQI,UAAU,UAAU,QAAQ,WAAW,SAAS,GAAG;EAClE,KAAK;IAAS;MAA0B,QAAQ;;EAEhD,IAAI,OAAO;;;iBAIG,aAAa,UAAU,QAAQ,MAAM,UAAU,GAAG;cAIrD,cAAc,UAAU,QAAQ,WAAW,qBAAqB,cAAc,UAAU,GAAG;;EAKtG,YAAY,QAAQ,GAAG;;EAMvB,KAAK;IAAS;MAA0B,QAAQ;MAgB5C,OAAO;;EAQX,IAAI,OAAO;MAoBP;;EAOJ;GAUC,YAAiC;;;iBAIpB,YAAY,aAAa,SAAS;;iBAKlC,kBAAkB,aAAa,SAAS,sBAAsB"}
1
+ {"version":3,"file":"LazyField.d.mts","names":[],"sources":["../../src/relations/LazyField.ts"],"mappings":";;;;;cAgBa;;;;;;;;cASA;;;;;;;UAQI,UAAU,UAAU,QAAQ,WAAW,SAAS,GAAG;EAClE,KAAK;IAAS;MAA0B,QAAQ;;EAEhD,IAAI,OAAO;;;iBAIG,aAAa,UAAU,QAAQ,MAAM,UAAU,GAAG;cAIrD,cAAc,UAAU,QAAQ,WAAW,qBAAqB,cAAc,UAAU,GAAG;;EAKtG,YAAY,QAAQ,GAAG;;EAMvB,KAAK;IAAS;MAA0B,QAAQ;MAgB5C,OAAO;;EAQX,IAAI,OAAO;MAoBP;;;;;;EAWJ;;EAYA;GAUC,YAAiC;;;iBAIpB,YAAY,aAAa,SAAS;;iBAKlC,kBAAkB,aAAa,SAAS,sBAAsB"}
@@ -71,6 +71,20 @@ var LazyFieldImpl = class extends AbstractPropertyImpl {
71
71
  if (getInstanceData(this.entity).isOrWasNew) return true;
72
72
  return this.#loaded;
73
73
  }
74
+ /**
75
+ * Marks this field loaded, using its value in entity data or reading it from the entity's row.
76
+ *
77
+ * The caller must know the value is present in entity data or the row; default SELECTs omit lazy columns.
78
+ */
79
+ preload() {
80
+ const { data, rowData, rowIndex } = getInstanceData(this.entity);
81
+ if (!(this.#fieldName in data)) {
82
+ const field = getMetadata(this.entity).allFields[this.#fieldName];
83
+ data[this.#fieldName] = field.serde.fromRow(rowData, rowIndex);
84
+ }
85
+ this.#loaded = true;
86
+ this.#loadPromise = void 0;
87
+ }
74
88
  /** Called after em.flush to invalidate the cached value, since the db state may have changed. */
75
89
  resetAfterFlush() {
76
90
  this.#loaded = false;
@@ -1 +1 @@
1
- {"version":3,"file":"LazyField.js","names":["#fieldName","#loaded","#loadPromise","#value"],"sources":["../../src/relations/LazyField.ts"],"sourcesContent":["import { getInstanceData } from \"src/BaseEntity.ts\";\nimport { type BatchLoader } from \"src/batchloaders/BatchLoader.ts\";\nimport { type Entity } from \"src/Entity.ts\";\nimport { type EntityManager } from \"src/EntityManager.ts\";\nimport { type EntityMetadata, type PrimitiveField, getMetadata } from \"src/EntityMetadata.ts\";\nimport { setField } from \"src/fields.ts\";\nimport { keyToNumber, tagId } from \"src/keys.ts\";\nimport { lazyField } from \"src/newEntity.ts\";\nimport type { ParsedFindQuery } from \"src/queries/find/QueryParser.ts\";\nimport { kqDot } from \"src/queries/sql/keywords.ts\";\nimport { AbstractPropertyImpl } from \"src/relations/AbstractPropertyImpl.ts\";\nimport { type LoadedProperty, type Property } from \"src/relations/hasProperty.ts\";\nimport { PropertyT } from \"src/relations/PropertySymbols.ts\";\nimport { PojoRowData } from \"src/RowData.ts\";\nimport { abbreviation } from \"src/utils.ts\";\n\nexport const lazyColumnLoadOperation = \"lazy-column-load\";\n\n/**\n * The \"original value\" of an unloaded lazy column: unknown, and never equal to any set value.\n *\n * Seeded into `__orm.data` by `LazyFieldImpl.set` so that setting an unloaded column (even to\n * `undefined`) always registers as a change, i.e. instead of comparing equal to the `undefined`\n * that the excluded column would otherwise read as.\n */\nexport const notLoadedValue = Symbol(\"notLoadedValue\");\n\n/**\n * A single-column primitive (i.e. a large `jsonb`/`text`) that is deliberately excluded from the\n * entity's default SELECT and only fetched from the database on demand.\n *\n * I.e. `await author.someBigColumn.load()`, or synchronously via `.get` after `em.populate`.\n */\nexport interface LazyField<T extends Entity, V> extends Property<T, V> {\n load(opts?: { forceReload?: boolean }): Promise<V>;\n /** Sets the value in memory (dirtied for the next flush), i.e. without loading the current db value first. */\n set(value: V): void;\n}\n\n/** Creates a `LazyField` for a column that codegen has marked `lazy: true`. */\nexport function hasLazyField<T extends Entity, V>(): LazyField<T, V> {\n return lazyField((entity: T, fieldName: string) => new LazyFieldImpl<T, V>(entity, fieldName));\n}\n\nexport class LazyFieldImpl<T extends Entity, V> extends AbstractPropertyImpl<T> implements LazyField<T, V> {\n #fieldName: string;\n #loaded = false;\n #loadPromise: Promise<V> | undefined;\n\n constructor(entity: T, fieldName: string) {\n super(entity);\n this.#fieldName = fieldName;\n }\n\n /** Fetches this column from the database (batched across peers) and caches it on the entity. */\n load(opts?: { forceReload?: boolean }): Promise<V> {\n const { entity } = this;\n if (opts?.forceReload) {\n this.#loaded = false;\n this.#loadPromise = undefined;\n }\n if (this.isLoaded) return Promise.resolve(this.#value);\n const meta = getMetadata(entity);\n return (this.#loadPromise ??= lazyColumnBatchLoader(entity.em, meta, this.#fieldName)\n .load(entity.idTagged)\n .then(() => {\n this.#loaded = true;\n return this.#value;\n }));\n }\n\n get get(): V {\n if (!this.isLoaded) {\n throw new Error(`${this.#fieldName} has not been loaded yet, use '.load()' or an 'em.populate' hint`);\n }\n return this.#value;\n }\n\n /** Sets the value in memory; the column is dirtied and written on the next `em.flush`. */\n set(value: V): void {\n if (this.isLoaded) {\n setField(this.entity, this.#fieldName, value);\n } else {\n // Seed `notLoadedValue` as the current value so the set always registers as a change against\n // the unknown db value: an unloaded column otherwise reads as `undefined`, which would make\n // `set(undefined)` a silent no-op, and a later `set(undefined)` falsely revert to clean.\n const { data } = getInstanceData(this.entity);\n data[this.#fieldName] = notLoadedValue;\n try {\n setField(this.entity, this.#fieldName, value);\n } catch (e) {\n // Don't leak the marker as a readable value, i.e. if setting on a deleted entity throws\n delete data[this.#fieldName];\n throw e;\n }\n }\n this.#loaded = true;\n }\n\n get isLoaded(): boolean {\n // New entities keep their value in-memory, so there's nothing to lazy-load.\n if (getInstanceData(this.entity).isOrWasNew) return true;\n return this.#loaded;\n }\n\n /** Called after em.flush to invalidate the cached value, since the db state may have changed. */\n resetAfterFlush(): void {\n this.#loaded = false;\n this.#loadPromise = undefined;\n }\n\n /** The domain value, which `load`/`set` keep in the entity's `__orm.data` like any other column. */\n get #value(): V {\n return getInstanceData(this.entity).data[this.#fieldName] as V;\n }\n\n [PropertyT] = undefined as any as T;\n}\n\n/** Type guard utility for determining if an entity field is a LazyField. */\nexport function isLazyField(maybe: any): maybe is LazyField<any, any> {\n return maybe instanceof LazyFieldImpl;\n}\n\n/** Type guard utility for determining if an entity field is a loaded LazyField. */\nexport function isLoadedLazyField(maybe: any): maybe is LazyField<any, any> & LoadedProperty<any, any> {\n return isLazyField(maybe) && maybe.isLoaded;\n}\n\n/** Batches `LazyField.load`s for the same column into a single `SELECT id, <column> ... WHERE id IN (...)`. */\nfunction lazyColumnBatchLoader(em: EntityManager, meta: EntityMetadata, fieldName: string): BatchLoader<string> {\n return em.getBatchLoader(lazyColumnLoadOperation, `${meta.tableName}-${fieldName}`, async (taggedIds) => {\n const field = meta.allFields[fieldName] as PrimitiveField;\n const column = field.serde.columns[0];\n const alias = abbreviation(meta.tableName);\n const keys = taggedIds.map((id) => keyToNumber(meta, id));\n const query: ParsedFindQuery = {\n selects: [`${kqDot(alias, \"id\")} as id`, kqDot(alias, column.columnName)],\n tables: [{ alias, join: \"primary\", table: meta.tableName }],\n condition: {\n kind: \"exp\",\n op: \"and\",\n conditions: [{ kind: \"column\", alias, column: \"id\", dbType: meta.idDbType, cond: { kind: \"in\", value: keys } }],\n },\n orderBys: [],\n };\n const rows = await em[\"executeFind\"](meta, lazyColumnLoadOperation, query, {});\n const rowData = new PojoRowData(rows);\n for (let i = 0; i < rows.length; i++) {\n const entity = em.findExistingInstance(tagId(meta, rows[i].id));\n if (entity) getInstanceData(entity).data[fieldName] = field.serde.fromRow(rowData, i);\n }\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"LazyField.js","names":["#fieldName","#loaded","#loadPromise","#value"],"sources":["../../src/relations/LazyField.ts"],"sourcesContent":["import { getInstanceData } from \"src/BaseEntity.ts\";\nimport { type BatchLoader } from \"src/batchloaders/BatchLoader.ts\";\nimport { type Entity } from \"src/Entity.ts\";\nimport { type EntityManager } from \"src/EntityManager.ts\";\nimport { type EntityMetadata, type PrimitiveField, getMetadata } from \"src/EntityMetadata.ts\";\nimport { setField } from \"src/fields.ts\";\nimport { keyToNumber, tagId } from \"src/keys.ts\";\nimport { lazyField } from \"src/newEntity.ts\";\nimport type { ParsedFindQuery } from \"src/queries/find/QueryParser.ts\";\nimport { kqDot } from \"src/queries/sql/keywords.ts\";\nimport { AbstractPropertyImpl } from \"src/relations/AbstractPropertyImpl.ts\";\nimport { type LoadedProperty, type Property } from \"src/relations/hasProperty.ts\";\nimport { PropertyT } from \"src/relations/PropertySymbols.ts\";\nimport { PojoRowData } from \"src/RowData.ts\";\nimport { abbreviation } from \"src/utils.ts\";\n\nexport const lazyColumnLoadOperation = \"lazy-column-load\";\n\n/**\n * The \"original value\" of an unloaded lazy column: unknown, and never equal to any set value.\n *\n * Seeded into `__orm.data` by `LazyFieldImpl.set` so that setting an unloaded column (even to\n * `undefined`) always registers as a change, i.e. instead of comparing equal to the `undefined`\n * that the excluded column would otherwise read as.\n */\nexport const notLoadedValue = Symbol(\"notLoadedValue\");\n\n/**\n * A single-column primitive (i.e. a large `jsonb`/`text`) that is deliberately excluded from the\n * entity's default SELECT and only fetched from the database on demand.\n *\n * I.e. `await author.someBigColumn.load()`, or synchronously via `.get` after `em.populate`.\n */\nexport interface LazyField<T extends Entity, V> extends Property<T, V> {\n load(opts?: { forceReload?: boolean }): Promise<V>;\n /** Sets the value in memory (dirtied for the next flush), i.e. without loading the current db value first. */\n set(value: V): void;\n}\n\n/** Creates a `LazyField` for a column that codegen has marked `lazy: true`. */\nexport function hasLazyField<T extends Entity, V>(): LazyField<T, V> {\n return lazyField((entity: T, fieldName: string) => new LazyFieldImpl<T, V>(entity, fieldName));\n}\n\nexport class LazyFieldImpl<T extends Entity, V> extends AbstractPropertyImpl<T> implements LazyField<T, V> {\n #fieldName: string;\n #loaded = false;\n #loadPromise: Promise<V> | undefined;\n\n constructor(entity: T, fieldName: string) {\n super(entity);\n this.#fieldName = fieldName;\n }\n\n /** Fetches this column from the database (batched across peers) and caches it on the entity. */\n load(opts?: { forceReload?: boolean }): Promise<V> {\n const { entity } = this;\n if (opts?.forceReload) {\n this.#loaded = false;\n this.#loadPromise = undefined;\n }\n if (this.isLoaded) return Promise.resolve(this.#value);\n const meta = getMetadata(entity);\n return (this.#loadPromise ??= lazyColumnBatchLoader(entity.em, meta, this.#fieldName)\n .load(entity.idTagged)\n .then(() => {\n this.#loaded = true;\n return this.#value;\n }));\n }\n\n get get(): V {\n if (!this.isLoaded) {\n throw new Error(`${this.#fieldName} has not been loaded yet, use '.load()' or an 'em.populate' hint`);\n }\n return this.#value;\n }\n\n /** Sets the value in memory; the column is dirtied and written on the next `em.flush`. */\n set(value: V): void {\n if (this.isLoaded) {\n setField(this.entity, this.#fieldName, value);\n } else {\n // Seed `notLoadedValue` as the current value so the set always registers as a change against\n // the unknown db value: an unloaded column otherwise reads as `undefined`, which would make\n // `set(undefined)` a silent no-op, and a later `set(undefined)` falsely revert to clean.\n const { data } = getInstanceData(this.entity);\n data[this.#fieldName] = notLoadedValue;\n try {\n setField(this.entity, this.#fieldName, value);\n } catch (e) {\n // Don't leak the marker as a readable value, i.e. if setting on a deleted entity throws\n delete data[this.#fieldName];\n throw e;\n }\n }\n this.#loaded = true;\n }\n\n get isLoaded(): boolean {\n // New entities keep their value in-memory, so there's nothing to lazy-load.\n if (getInstanceData(this.entity).isOrWasNew) return true;\n return this.#loaded;\n }\n\n /**\n * Marks this field loaded, using its value in entity data or reading it from the entity's row.\n *\n * The caller must know the value is present in entity data or the row; default SELECTs omit lazy columns.\n */\n preload(): void {\n const { data, rowData, rowIndex } = getInstanceData(this.entity);\n if (!(this.#fieldName in data)) {\n const field = getMetadata(this.entity).allFields[this.#fieldName];\n data[this.#fieldName] = field.serde!.fromRow(rowData, rowIndex);\n }\n\n this.#loaded = true;\n this.#loadPromise = undefined;\n }\n\n /** Called after em.flush to invalidate the cached value, since the db state may have changed. */\n resetAfterFlush(): void {\n this.#loaded = false;\n this.#loadPromise = undefined;\n }\n\n /** The domain value, which `load`/`set` keep in the entity's `__orm.data` like any other column. */\n get #value(): V {\n return getInstanceData(this.entity).data[this.#fieldName] as V;\n }\n\n [PropertyT] = undefined as any as T;\n}\n\n/** Type guard utility for determining if an entity field is a LazyField. */\nexport function isLazyField(maybe: any): maybe is LazyField<any, any> {\n return maybe instanceof LazyFieldImpl;\n}\n\n/** Type guard utility for determining if an entity field is a loaded LazyField. */\nexport function isLoadedLazyField(maybe: any): maybe is LazyField<any, any> & LoadedProperty<any, any> {\n return isLazyField(maybe) && maybe.isLoaded;\n}\n\n/** Batches `LazyField.load`s for the same column into a single `SELECT id, <column> ... WHERE id IN (...)`. */\nfunction lazyColumnBatchLoader(em: EntityManager, meta: EntityMetadata, fieldName: string): BatchLoader<string> {\n return em.getBatchLoader(lazyColumnLoadOperation, `${meta.tableName}-${fieldName}`, async (taggedIds) => {\n const field = meta.allFields[fieldName] as PrimitiveField;\n const column = field.serde.columns[0];\n const alias = abbreviation(meta.tableName);\n const keys = taggedIds.map((id) => keyToNumber(meta, id));\n const query: ParsedFindQuery = {\n selects: [`${kqDot(alias, \"id\")} as id`, kqDot(alias, column.columnName)],\n tables: [{ alias, join: \"primary\", table: meta.tableName }],\n condition: {\n kind: \"exp\",\n op: \"and\",\n conditions: [{ kind: \"column\", alias, column: \"id\", dbType: meta.idDbType, cond: { kind: \"in\", value: keys } }],\n },\n orderBys: [],\n };\n const rows = await em[\"executeFind\"](meta, lazyColumnLoadOperation, query, {});\n const rowData = new PojoRowData(rows);\n for (let i = 0; i < rows.length; i++) {\n const entity = em.findExistingInstance(tagId(meta, rows[i].id));\n if (entity) getInstanceData(entity).data[fieldName] = field.serde.fromRow(rowData, i);\n }\n });\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.14",
3
+ "version": "2.4.0-next.16",
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.14"
45
+ "joist-utils": "2.4.0-next.16"
46
46
  },
47
47
  "dependencies": {
48
48
  "ansis": "^4.3.1",