joist-core 2.3.0-next.63 → 2.3.0-next.65
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/Aliases.cjs +11 -10
- package/build/Aliases.cjs.map +1 -1
- package/build/Aliases.d.cts +19 -1
- package/build/Aliases.d.cts.map +1 -1
- package/build/Aliases.d.mts +19 -1
- package/build/Aliases.d.mts.map +1 -1
- package/build/Aliases.js +2 -1
- package/build/Aliases.js.map +1 -1
- package/build/ConditionBuilder.cjs +2 -3
- package/build/ConditionBuilder.cjs.map +1 -1
- package/build/ConditionBuilder.js +1 -2
- package/build/ConditionBuilder.js.map +1 -1
- package/build/DeferredAlias.cjs +2 -9
- package/build/DeferredAlias.cjs.map +1 -1
- package/build/DeferredAlias.d.cts +3 -5
- package/build/DeferredAlias.d.cts.map +1 -1
- package/build/DeferredAlias.d.mts +3 -5
- package/build/DeferredAlias.d.mts.map +1 -1
- package/build/DeferredAlias.js +3 -9
- package/build/DeferredAlias.js.map +1 -1
- package/build/EntityMetadata.cjs.map +1 -1
- package/build/EntityMetadata.d.cts +2 -1
- package/build/EntityMetadata.d.cts.map +1 -1
- package/build/EntityMetadata.d.mts +2 -1
- package/build/EntityMetadata.d.mts.map +1 -1
- package/build/EntityMetadata.js.map +1 -1
- package/build/Expr.cjs +6 -20
- package/build/Expr.cjs.map +1 -1
- package/build/Expr.d.cts +7 -9
- package/build/Expr.d.cts.map +1 -1
- package/build/Expr.d.mts +7 -9
- package/build/Expr.d.mts.map +1 -1
- package/build/Expr.js +5 -15
- package/build/Expr.js.map +1 -1
- package/build/QueryParser.cjs +1 -1
- package/build/QueryParser.cjs.map +1 -1
- package/build/QueryParser.js +1 -1
- package/build/QueryParser.js.map +1 -1
- package/build/Tables.cjs +133 -168
- package/build/Tables.cjs.map +1 -1
- package/build/Tables.d.cts +43 -7
- package/build/Tables.d.cts.map +1 -1
- package/build/Tables.d.mts +43 -7
- package/build/Tables.d.mts.map +1 -1
- package/build/Tables.js +115 -150
- package/build/Tables.js.map +1 -1
- package/build/configure.cjs +1 -4
- package/build/configure.cjs.map +1 -1
- package/build/configure.js +1 -4
- package/build/configure.js.map +1 -1
- package/build/drivers/buildUtils.cjs +5 -1
- package/build/drivers/buildUtils.cjs.map +1 -1
- package/build/drivers/buildUtils.d.cts +4 -2
- package/build/drivers/buildUtils.d.cts.map +1 -1
- package/build/drivers/buildUtils.d.mts +4 -2
- package/build/drivers/buildUtils.d.mts.map +1 -1
- package/build/drivers/buildUtils.js +5 -2
- package/build/drivers/buildUtils.js.map +1 -1
- package/build/drivers/index.cjs +1 -0
- package/build/drivers/index.d.cts +2 -2
- package/build/drivers/index.d.mts +2 -2
- package/build/drivers/index.js +2 -2
- package/build/index.cjs +3 -2
- package/build/index.cjs.map +1 -1
- package/build/index.d.cts +4 -4
- package/build/index.d.cts.map +1 -1
- package/build/index.d.mts +4 -4
- package/build/index.d.mts.map +1 -1
- package/build/index.js +3 -3
- package/build/index.js.map +1 -1
- package/build/query.cjs +26 -52
- package/build/query.cjs.map +1 -1
- package/build/query.d.cts +12 -8
- package/build/query.d.cts.map +1 -1
- package/build/query.d.mts +12 -8
- package/build/query.d.mts.map +1 -1
- package/build/query.js +26 -52
- package/build/query.js.map +1 -1
- package/build/serde.cjs +5 -2
- package/build/serde.cjs.map +1 -1
- package/build/serde.d.cts +2 -1
- package/build/serde.d.cts.map +1 -1
- package/build/serde.d.mts +2 -1
- package/build/serde.d.mts.map +1 -1
- package/build/serde.js +5 -2
- package/build/serde.js.map +1 -1
- package/build/skipCondition.cjs +15 -0
- package/build/skipCondition.cjs.map +1 -0
- package/build/skipCondition.d.cts +7 -0
- package/build/skipCondition.d.cts.map +1 -0
- package/build/skipCondition.d.mts +7 -0
- package/build/skipCondition.d.mts.map +1 -0
- package/build/skipCondition.js +14 -0
- package/build/skipCondition.js.map +1 -0
- package/build/typeMap.d.cts +3 -1
- package/build/typeMap.d.cts.map +1 -1
- package/build/typeMap.d.mts +3 -1
- package/build/typeMap.d.mts.map +1 -1
- package/package.json +2 -2
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { EntityMetadata } from "./EntityMetadata.mjs";
|
|
2
2
|
import { ColumnCondition, RawCondition } from "./QueryParser.mjs";
|
|
3
3
|
//#region src/DeferredAlias.d.ts
|
|
4
|
-
/** An undefined predicate is pruned by either query parser. */
|
|
5
|
-
declare const skipCondition: ColumnCondition;
|
|
6
4
|
declare const deferredAliasSym: unique symbol;
|
|
7
|
-
/** Resolves a domain alias
|
|
5
|
+
/** Resolves a domain alias to this em.find parse's binding. */
|
|
8
6
|
type AliasResolver = (handle: {
|
|
9
7
|
meta: EntityMetadata;
|
|
10
8
|
tableName: string;
|
|
@@ -12,7 +10,7 @@ type AliasResolver = (handle: {
|
|
|
12
10
|
meta: EntityMetadata;
|
|
13
11
|
alias: string;
|
|
14
12
|
};
|
|
15
|
-
/** A condition whose SQL aliases are resolved afresh for each occurrence in
|
|
13
|
+
/** A domain condition whose SQL aliases are resolved afresh for each occurrence in em.find. */
|
|
16
14
|
interface DeferredAliasCondition<C = ColumnCondition | RawCondition> {
|
|
17
15
|
[deferredAliasSym]: (resolve: AliasResolver) => C;
|
|
18
16
|
}
|
|
@@ -21,5 +19,5 @@ declare function isDeferredAliasCondition<C>(cond: C): cond is C & DeferredAlias
|
|
|
21
19
|
/** Tags a condition without changing its enumerable shape; resolution only mutates a fresh copy. */
|
|
22
20
|
declare function withDeferredAlias<C extends object>(cond: C, resolve: (r: AliasResolver, copy: C) => void): C & DeferredAliasCondition<C>;
|
|
23
21
|
//#endregion
|
|
24
|
-
export { AliasResolver, DeferredAliasCondition, deferredAliasSym, isDeferredAliasCondition,
|
|
22
|
+
export { AliasResolver, DeferredAliasCondition, deferredAliasSym, isDeferredAliasCondition, withDeferredAlias };
|
|
25
23
|
//# sourceMappingURL=DeferredAlias.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DeferredAlias.d.mts","names":[],"sources":["../src/DeferredAlias.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"DeferredAlias.d.mts","names":[],"sources":["../src/DeferredAlias.ts"],"mappings":";;;cAIa;;KAGD,iBAAiB;EAAU,MAAM;EAAgB;;EAC3D,MAAM;EACN;;;UAIe,uBAAuB,IAAI,kBAAkB;GAC3D,oBAAoB,SAAS,kBAAkB;;;iBAIlC,yBAAyB,GAAG,MAAM,IAAI,QAAQ,IAAI,uBAAuB;;iBAKzE,kBAAkB,kBAChC,MAAM,GACN,UAAU,GAAG,eAAe,MAAM,aACjC,IAAI,uBAAuB"}
|
package/build/DeferredAlias.js
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
|
+
import "./EntityMetadata.js";
|
|
2
|
+
import "./QueryParser.js";
|
|
1
3
|
//#region src/DeferredAlias.ts
|
|
2
|
-
/** An undefined predicate is pruned by either query parser. */
|
|
3
|
-
const skipCondition = {
|
|
4
|
-
kind: "column",
|
|
5
|
-
alias: "skip",
|
|
6
|
-
column: "skip",
|
|
7
|
-
dbType: "skip",
|
|
8
|
-
cond: void 0
|
|
9
|
-
};
|
|
10
4
|
const deferredAliasSym = Symbol("joist.deferredAliasCondition");
|
|
11
5
|
/** Detects conditions that carry a per-parse alias resolver. */
|
|
12
6
|
function isDeferredAliasCondition(cond) {
|
|
@@ -24,6 +18,6 @@ function withDeferredAlias(cond, resolve) {
|
|
|
24
18
|
});
|
|
25
19
|
}
|
|
26
20
|
//#endregion
|
|
27
|
-
export { deferredAliasSym, isDeferredAliasCondition,
|
|
21
|
+
export { deferredAliasSym, isDeferredAliasCondition, withDeferredAlias };
|
|
28
22
|
|
|
29
23
|
//# sourceMappingURL=DeferredAlias.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DeferredAlias.js","names":[],"sources":["../src/DeferredAlias.ts"],"sourcesContent":["// Erased imports keep domain predicates
|
|
1
|
+
{"version":3,"file":"DeferredAlias.js","names":[],"sources":["../src/DeferredAlias.ts"],"sourcesContent":["// Erased imports keep domain predicates independent of the query parser at module load time.\nimport { type EntityMetadata } from \"./EntityMetadata.ts\";\nimport { type ColumnCondition, type RawCondition } from \"./QueryParser.ts\";\n\nexport const deferredAliasSym: unique symbol = Symbol(\"joist.deferredAliasCondition\");\n\n/** Resolves a domain alias to this em.find parse's binding. */\nexport type AliasResolver = (handle: { meta: EntityMetadata; tableName: string }) => {\n meta: EntityMetadata;\n alias: string;\n};\n\n/** A domain condition whose SQL aliases are resolved afresh for each occurrence in em.find. */\nexport interface DeferredAliasCondition<C = ColumnCondition | RawCondition> {\n [deferredAliasSym]: (resolve: AliasResolver) => C;\n}\n\n/** Detects conditions that carry a per-parse alias resolver. */\nexport function isDeferredAliasCondition<C>(cond: C): cond is C & DeferredAliasCondition<C> {\n return typeof cond === \"object\" && cond !== null && deferredAliasSym in cond;\n}\n\n/** Tags a condition without changing its enumerable shape; resolution only mutates a fresh copy. */\nexport function withDeferredAlias<C extends object>(\n cond: C,\n resolve: (r: AliasResolver, copy: C) => void,\n): C & DeferredAliasCondition<C> {\n return Object.defineProperty(cond, deferredAliasSym, {\n value: (r: AliasResolver) => {\n const copy = { ...cond };\n resolve(r, copy);\n return copy;\n },\n enumerable: false,\n }) as C & DeferredAliasCondition<C>;\n}\n"],"mappings":";;;AAIA,MAAa,mBAAkC,OAAO,8BAA8B;;AAcpF,SAAgB,yBAA4B,MAAgD;CAC1F,OAAO,OAAO,SAAS,YAAY,SAAS,QAAQ,oBAAoB;AAC1E;;AAGA,SAAgB,kBACd,MACA,SAC+B;CAC/B,OAAO,OAAO,eAAe,MAAM,kBAAkB;EACnD,QAAQ,MAAqB;GAC3B,MAAM,OAAO,EAAE,GAAG,KAAK;GACvB,QAAQ,GAAG,IAAI;GACf,OAAO;EACT;EACA,YAAY;CACd,CAAC;AACH"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EntityMetadata.cjs","names":["getInstanceData","getMetadataForType","isEntity"],"sources":["../src/EntityMetadata.ts"],"sourcesContent":["import { getInstanceData } from \"./BaseEntity.ts\";\nimport { type ConfigApi, type Reactable, type ReactiveRule } from \"./config.ts\";\nimport { getMetadataForType } from \"./configure.ts\";\nimport { type Entity, isEntity } from \"./Entity.ts\";\nimport { type EntityManager, type MaybeAbstractEntityConstructor, type TimestampFields } from \"./EntityManager.ts\";\nimport { type EnumMetadata } from \"./EnumMetadata.ts\";\nimport { type DeepNew } from \"./loadHints.ts\";\nimport { type FieldSerde, type PolymorphicKeySerde } from \"./serde.ts\";\n\nexport function getMetadata<T extends Entity>(entity: T): EntityMetadata<T>;\nexport function getMetadata<T extends Entity>(type: MaybeAbstractEntityConstructor<T>): EntityMetadata<T>;\nexport function getMetadata<T extends Entity>(meta: EntityMetadata): EntityMetadata;\nexport function getMetadata<T extends Entity>(\n param: T | MaybeAbstractEntityConstructor<T> | EntityMetadata,\n): EntityMetadata {\n if (!param) throw new Error(`Cannot getMetadata of ${param}`);\n return (\n typeof param === \"function\" ? (param as any).metadata : \"cstr\" in param ? param : getInstanceData(param).metadata\n ) as EntityMetadata;\n}\n\n/** Returns the metadata layer that declares `fieldName`, i.e. `Task.tags` for `TaskOld.tags`. */\nexport function getMetadataForField(meta: EntityMetadata, fieldName: string): EntityMetadata {\n if (!meta.allFields[fieldName]) throw new Error(`Field '${fieldName}' not found on ${meta.type}`);\n // I.e. `TaskOld.tags` is an STI-inherited `Task.tags` and should walk up, but `SmallPublisherGroup.publishers`\n // is a CTI-specialized `PublisherGroup.publishers` and should stay on `SmallPublisherGroup`.\n while (!(fieldName in meta.fields) && meta.allFields[fieldName]?.specialized !== true && meta.baseType) {\n meta = getMetadataForType(meta.baseType);\n }\n return meta;\n}\n\n/**\n * Runtime metadata about an entity.\n *\n * The `joist-codegen` step will generate this by reading the database schema at build/codegen\n * time, along with any customizations in `joist-config.json`.\n *\n * Note: This has no generic, like `T extends Entity`, because `Entity<IdType>` i.e. with\n * an unknown string/id type, causes issues when we want to generically mix `EntityMetadata`\n * of different types, that even liberally using `EntityMetadata<any>` did not avoid.\n */\nexport interface EntityMetadata<T extends Entity = any> {\n cstr: MaybeAbstractEntityConstructor<T>;\n type: string;\n /** How the domain entity's id is represented. */\n idType: \"tagged-string\" | \"untagged-string\" | \"number\";\n /** The database column type, i.e. used to do `::type` casts in Postgres. */\n idDbType: \"bigint\" | \"int\" | \"uuid\" | \"text\";\n tableName: string;\n /** Whether codegen verified this table is a supported SQL mutation target; does not gate reads. */\n supportsEmExecute?: boolean;\n /** If we're a subtype, our immediate base type's name, e.g. for `SmallPublisher` this would be `Publisher`. */\n baseType: string | undefined;\n inheritanceType?: \"sti\" | \"cti\" | undefined;\n /** Indicates the field to use to derive which subtype to instantiate; only set on the base meta. */\n stiDiscriminatorField?: string;\n /** The discriminator enum value for this subtype; only set on sub metas. */\n stiDiscriminatorValue?: number;\n /** Whether this type is abstract (cannot be instantiated directly). */\n ctiAbstract?: boolean;\n tagName: string;\n fields: Record<string, Field>;\n /** Physical column names mapped to their owning domain fields, including inherited fields. */\n columns: Record<string, { fieldName: string }>;\n allFields: Record<string, Field & { aliasSuffix: string; specialized?: true }>;\n /** Usually polys are in `allFields`, but we pull the components out for comp-specific finds, like `parentBook`. */\n polyComponentFields?: Record<string, Field & { aliasSuffix: string }>;\n // Using `any` to avoid type errors between BaseType.metadata & SubType.metadata static fields\n config: ConfigApi<any, any>;\n /** The lazy list of non-read-only reactables for this metadata and its base types. */\n reactables?: Reactable[];\n /** The lazy lookup of non-read-only reactables by source field name. */\n reactablesByField?: ReadonlyMap<string, Reactable[]>;\n /** The lazy list of all reactables for this metadata and its base types. */\n reactablesIncludingReadOnly?: Reactable[];\n /** The lazy lookup of all reactables by source field name. */\n reactablesIncludingReadOnlyByField?: ReadonlyMap<string, Reactable[]>;\n /** The lazy list of reactive validation rules for this metadata and its subtypes. */\n reactiveRules?: ReactiveRule[];\n /** The lazy list of reactive *commit* rules (post-flush/pre-commit) for this metadata and its subtypes. */\n reactiveCommitRules?: ReactiveRule[];\n /** Lazily-cached: whether flushing an entity of this type could trigger any `addCommitRule` work. */\n hasCommitRules?: boolean;\n /** The lazily-cached set of own `lazy` primitive field names, i.e. columns excluded from the default SELECT. */\n lazyFieldNames?: ReadonlySet<string>;\n /** The lazily-cached list of sync-derived (`hasReactiveField`-less) field names, recalced during flush. */\n syncDerivedFields?: string[];\n /** Lazily-cached: whether this entity has any `lazy` columns, i.e. so the default SELECT lists columns explicitly. */\n hasLazyColumns?: boolean;\n orderBy: string | undefined;\n /** Flat field names that can be used to find existing rows before creating. I.e. [[\"email\"], [\"author\", \"title\"]]. */\n uniqueBy?: string[][];\n timestampFields: TimestampFields | undefined;\n // Ideally this would be the application-specific EntityManager (to avoid anys), but it doesn't really matter\n factory: (em: EntityManager<any, any, any>, opts?: any) => DeepNew<T>;\n /** The list of base types for this subtype, e.g. for Dog it'd be [Animal, Mammal]. */\n baseTypes: EntityMetadata[];\n /** The list of subtypes for this base type, e.g. for Animal it'd be `[Mammal, Dog]`. */\n subTypes: EntityMetadata[];\n /** Cached `[...baseTypes, this]` set up by `configureMetadata`, so hot paths avoid re-allocating it. */\n baseSelfMetas?: EntityMetadata[];\n /** Cached `[...baseTypes, this, ...subTypes]` set up by `configureMetadata`, so hot paths avoid re-allocating it. */\n baseSelfAndSubMetas?: EntityMetadata[];\n /** The lazy lookup of subtypes by type name, i.e. `Animal.metadata.subTypesByType.get(\"Dog\")`. */\n subTypesByType?: ReadonlyMap<string, EntityMetadata>;\n /** The lazy lookup of STI subtypes by discriminator value, i.e. `Task.metadata.subTypesByStiValue.get(1)`. */\n subTypesByStiValue?: ReadonlyMap<unknown, EntityMetadata>;\n /** The lazy STI discriminator column name, i.e. `Task.metadata.stiDiscriminatorColumnName`. */\n stiDiscriminatorColumnName?: string;\n /** If the schema is lacking deferred FKs, this is our insertion order. */\n nonDeferredFkOrder?: number;\n}\n\nexport type Field =\n | PrimaryKeyField\n | PrimitiveField\n | EnumField\n | OneToManyField\n | LargeOneToManyField\n | ManyToOneField\n | ManyToManyField\n | ManyToManyEnumField\n | OneToOneField\n | PolymorphicField;\n\n// Only the fields that have defined `serde` keys; should be a mapped type of Field\nexport type SerdeField = PrimaryKeyField | PrimitiveField | EnumField | ManyToOneField | PolymorphicField;\n\nexport type PrimaryKeyField = {\n kind: \"primaryKey\";\n fieldName: string;\n fieldIdName: undefined;\n required: true;\n serde: FieldSerde;\n immutable: true;\n};\n\nexport type PrimitiveField = {\n kind: \"primitive\";\n fieldName: string;\n fieldIdName: undefined;\n required: boolean;\n derived: \"orm\" | \"sync\" | \"async\" | false;\n protected: boolean;\n type: string | Function;\n serde: FieldSerde;\n immutable: boolean;\n citext?: boolean;\n default?: \"schema\" | \"config\";\n sanitize?: boolean;\n /** When true, this column is excluded from the entity's default SELECT and lazy-loaded via a `LazyField`. */\n lazy?: boolean;\n};\n\nexport type EnumField = {\n kind: \"enum\";\n fieldName: string;\n fieldIdName: undefined;\n required: boolean;\n derived: \"sync\" | \"async\" | false;\n enumDetailType: { getValues(): ReadonlyArray<unknown>; findById(id: any): unknown };\n serde: FieldSerde;\n immutable: boolean;\n default?: \"schema\" | \"config\";\n};\n\nexport type OneToManyField = {\n kind: \"o2m\";\n fieldName: string;\n fieldIdName: string;\n required: boolean;\n otherMetadata: () => EntityMetadata;\n otherFieldName: string;\n /** Useful when our other side is a poly, to find which component points back to us. */\n otherColumnName: string;\n serde: undefined;\n immutable: false;\n orderBy?: { field: string; direction: \"ASC\" | \"DESC\" };\n /** When `\"include\"`, this collection's `.get`/`.load` returns soft-deleted entities instead of hiding them. */\n softDeletes?: \"include\" | \"exclude\";\n};\n\nexport type LargeOneToManyField = {\n kind: \"lo2m\";\n fieldName: string;\n fieldIdName: string;\n required: boolean;\n otherMetadata: () => EntityMetadata;\n otherFieldName: string;\n /** Useful when our other side is a poly, to find which component points back to us. */\n otherColumnName: string;\n serde: undefined;\n immutable: false;\n};\n\nexport type ManyToOneField = {\n kind: \"m2o\";\n fieldName: string;\n fieldIdName: string;\n required: boolean;\n otherMetadata: () => EntityMetadata;\n otherFieldName: string;\n serde: FieldSerde;\n immutable: boolean;\n derived: \"async\" | false;\n default?: \"schema\" | \"config\";\n};\n\nexport type ManyToManyField = {\n kind: \"m2m\";\n fieldName: string;\n fieldIdName: string;\n required: boolean;\n derived: \"async\" | \"otherSide\" | false;\n otherMetadata: () => EntityMetadata;\n otherFieldName: string;\n serde: undefined;\n immutable: false;\n joinTableName: string;\n columnNames: [string, string];\n /** Whether the join table has a surrogate `id` PK; if false the FK pair is the composite PK. */\n hasJoinTableId: boolean;\n /** When `\"include\"`, this collection's `.get`/`.load` returns soft-deleted entities instead of hiding them. */\n softDeletes?: \"include\" | \"exclude\";\n};\n\n/**\n * A many-to-many between an entity and an enum \"table\", e.g. `Publisher.logoColors` backed by a\n * `publisher_logo_colors` join table joining `publishers` to the `color` enum table.\n *\n * To the user this looks like an array of enum codes (`Color[]`), but unlike an enum-array column it\n * is lazy (needs a load hint) because it lives in its own join table.\n */\nexport type ManyToManyEnumField = {\n kind: \"m2mEnum\";\n fieldName: string;\n fieldIdName: undefined;\n required: false;\n derived: false;\n /** The enum's metadata, used to map enum codes <-> their numeric ids. */\n enumDetailType: EnumMetadata<any, any, number>;\n serde: undefined;\n immutable: false;\n joinTableName: string;\n /** `[entityColumn, enumColumn]`, e.g. `[\"publisher_id\", \"color_id\"]`. */\n columnNames: [string, string];\n /** Whether the join table has a surrogate `id` PK; if false the FK pair is the composite PK. */\n hasJoinTableId: boolean;\n};\n\nexport type OneToOneField = {\n kind: \"o2o\";\n fieldName: string;\n fieldIdName: string;\n required: boolean;\n otherMetadata: () => EntityMetadata;\n otherFieldName: string;\n /** Useful when our other side is a poly, to find which component points back to us. */\n otherColumnName: string;\n serde: undefined;\n immutable: false;\n};\n\nexport type PolymorphicField = {\n kind: \"poly\";\n fieldName: string; // eg `parent`\n fieldIdName: string; // `parentId`\n required: boolean;\n components: PolymorphicFieldComponent[];\n serde: PolymorphicKeySerde;\n immutable: boolean;\n};\n\nexport type PolymorphicFieldComponent = {\n otherMetadata: () => EntityMetadata;\n otherFieldName: string; // eg `comment` or `comments`\n columnName: string; // eg `parent_book_id` or `parent_book_review_id`\n};\n\nexport function isOneToManyField(ormField: Field): ormField is OneToManyField {\n return ormField.kind === \"o2m\";\n}\n\nexport function isManyToOneField(ormField: Field): ormField is ManyToOneField {\n return ormField.kind === \"m2o\";\n}\n\nexport function isManyToManyField(ormField: Field): ormField is ManyToManyField {\n return ormField.kind === \"m2m\";\n}\n\nexport function isManyToManyEnumField(ormField: Field): ormField is ManyToManyEnumField {\n return ormField.kind === \"m2mEnum\";\n}\n\nexport function isOneToOneField(ormField: Field): ormField is OneToOneField {\n return ormField.kind === \"o2o\";\n}\n\nexport function isPolymorphicField(ormField: Field): ormField is PolymorphicField {\n return ormField.kind === \"poly\";\n}\n\nexport function isReferenceField(ormField: Field): ormField is ManyToOneField | OneToOneField | PolymorphicField {\n return ormField.kind === \"m2o\" || ormField.kind === \"o2o\" || ormField.kind === \"poly\";\n}\n\nexport function isCollectionField(ormField: Field): ormField is OneToManyField | ManyToManyField {\n return ormField.kind === \"o2m\" || ormField.kind === \"m2m\";\n}\n\nexport function getBaseAndSelfMetas(meta: EntityMetadata): EntityMetadata[];\nexport function getBaseAndSelfMetas(entity: Entity): EntityMetadata[];\nexport function getBaseAndSelfMetas(param: Entity | EntityMetadata): EntityMetadata[] {\n return isEntity(param)\n ? getBaseSelfAndSubMetas(getMetadata(param))\n : (param.baseSelfMetas ?? [...param.baseTypes, param]);\n}\n\nexport function getBaseSelfAndSubMetas(meta: EntityMetadata): EntityMetadata[] {\n return meta.baseSelfAndSubMetas ?? [...meta.baseTypes, meta, ...meta.subTypes];\n}\n\nexport function getSubMetas(meta: EntityMetadata): EntityMetadata[] {\n // We should do recursion at some point\n return meta.subTypes;\n}\n\nexport function getBaseMeta(meta: EntityMetadata): EntityMetadata {\n if (!meta.baseType) {\n return meta;\n } else {\n return meta.baseTypes[0];\n }\n}\n"],"mappings":";;;;;;;;;AAYA,SAAgB,YACd,OACgB;CAChB,IAAI,CAAC,OAAO,MAAM,IAAI,MAAM,yBAAyB,OAAO;CAC5D,OACE,OAAO,UAAU,aAAc,MAAc,WAAW,UAAU,QAAQ,QAAQA,mBAAAA,gBAAgB,KAAK,CAAC,CAAC;AAE7G;;AAGA,SAAgB,oBAAoB,MAAsB,WAAmC;CAC3F,IAAI,CAAC,KAAK,UAAU,YAAY,MAAM,IAAI,MAAM,UAAU,UAAU,iBAAiB,KAAK,MAAM;CAGhG,OAAO,EAAE,aAAa,KAAK,WAAW,KAAK,UAAU,UAAU,EAAE,gBAAgB,QAAQ,KAAK,UAC5F,OAAOC,kBAAAA,mBAAmB,KAAK,QAAQ;CAEzC,OAAO;AACT;AA0PA,SAAgB,iBAAiB,UAA6C;CAC5E,OAAO,SAAS,SAAS;AAC3B;AAEA,SAAgB,iBAAiB,UAA6C;CAC5E,OAAO,SAAS,SAAS;AAC3B;AAEA,SAAgB,kBAAkB,UAA8C;CAC9E,OAAO,SAAS,SAAS;AAC3B;AAEA,SAAgB,sBAAsB,UAAkD;CACtF,OAAO,SAAS,SAAS;AAC3B;AAEA,SAAgB,gBAAgB,UAA4C;CAC1E,OAAO,SAAS,SAAS;AAC3B;AAEA,SAAgB,mBAAmB,UAA+C;CAChF,OAAO,SAAS,SAAS;AAC3B;AAEA,SAAgB,iBAAiB,UAAgF;CAC/G,OAAO,SAAS,SAAS,SAAS,SAAS,SAAS,SAAS,SAAS,SAAS;AACjF;AAEA,SAAgB,kBAAkB,UAA+D;CAC/F,OAAO,SAAS,SAAS,SAAS,SAAS,SAAS;AACtD;AAIA,SAAgB,oBAAoB,OAAkD;CACpF,OAAOC,eAAAA,SAAS,KAAK,IACjB,uBAAuB,YAAY,KAAK,CAAC,IACxC,MAAM,iBAAiB,CAAC,GAAG,MAAM,WAAW,KAAK;AACxD;AAEA,SAAgB,uBAAuB,MAAwC;CAC7E,OAAO,KAAK,uBAAuB;EAAC,GAAG,KAAK;EAAW;EAAM,GAAG,KAAK;CAAQ;AAC/E;AAEA,SAAgB,YAAY,MAAwC;CAElE,OAAO,KAAK;AACd;AAEA,SAAgB,YAAY,MAAsC;CAChE,IAAI,CAAC,KAAK,UACR,OAAO;MAEP,OAAO,KAAK,UAAU;AAE1B"}
|
|
1
|
+
{"version":3,"file":"EntityMetadata.cjs","names":["getInstanceData","getMetadataForType","isEntity"],"sources":["../src/EntityMetadata.ts"],"sourcesContent":["import { getInstanceData } from \"./BaseEntity.ts\";\nimport { type ConfigApi, type Reactable, type ReactiveRule } from \"./config.ts\";\nimport { getMetadataForType } from \"./configure.ts\";\nimport { type Entity, isEntity } from \"./Entity.ts\";\nimport { type EntityManager, type MaybeAbstractEntityConstructor, type TimestampFields } from \"./EntityManager.ts\";\nimport { type EnumMetadata } from \"./EnumMetadata.ts\";\nimport { type DeepNew } from \"./loadHints.ts\";\nimport { type FieldSerde, type PolymorphicKeySerde } from \"./serde.ts\";\n\nexport function getMetadata<T extends Entity>(entity: T): EntityMetadata<T>;\nexport function getMetadata<T extends Entity>(type: MaybeAbstractEntityConstructor<T>): EntityMetadata<T>;\nexport function getMetadata<T extends Entity>(meta: EntityMetadata): EntityMetadata;\nexport function getMetadata<T extends Entity>(\n param: T | MaybeAbstractEntityConstructor<T> | EntityMetadata,\n): EntityMetadata {\n if (!param) throw new Error(`Cannot getMetadata of ${param}`);\n return (\n typeof param === \"function\" ? (param as any).metadata : \"cstr\" in param ? param : getInstanceData(param).metadata\n ) as EntityMetadata;\n}\n\n/** Returns the metadata layer that declares `fieldName`, i.e. `Task.tags` for `TaskOld.tags`. */\nexport function getMetadataForField(meta: EntityMetadata, fieldName: string): EntityMetadata {\n if (!meta.allFields[fieldName]) throw new Error(`Field '${fieldName}' not found on ${meta.type}`);\n // I.e. `TaskOld.tags` is an STI-inherited `Task.tags` and should walk up, but `SmallPublisherGroup.publishers`\n // is a CTI-specialized `PublisherGroup.publishers` and should stay on `SmallPublisherGroup`.\n while (!(fieldName in meta.fields) && meta.allFields[fieldName]?.specialized !== true && meta.baseType) {\n meta = getMetadataForType(meta.baseType);\n }\n return meta;\n}\n\n/**\n * Runtime metadata about an entity.\n *\n * The `joist-codegen` step will generate this by reading the database schema at build/codegen\n * time, along with any customizations in `joist-config.json`.\n *\n * Note: This has no generic, like `T extends Entity`, because `Entity<IdType>` i.e. with\n * an unknown string/id type, causes issues when we want to generically mix `EntityMetadata`\n * of different types, that even liberally using `EntityMetadata<any>` did not avoid.\n */\nexport interface EntityMetadata<T extends Entity = any> {\n cstr: MaybeAbstractEntityConstructor<T>;\n type: string;\n /** How the domain entity's id is represented. */\n idType: \"tagged-string\" | \"untagged-string\" | \"number\";\n /** The database column type, i.e. used to do `::type` casts in Postgres. */\n idDbType: \"bigint\" | \"int\" | \"uuid\" | \"text\";\n tableName: string;\n /** Whether codegen verified this table is a supported SQL mutation target; does not gate reads. */\n supportsEmExecute?: boolean;\n /** If we're a subtype, our immediate base type's name, e.g. for `SmallPublisher` this would be `Publisher`. */\n baseType: string | undefined;\n inheritanceType?: \"sti\" | \"cti\" | undefined;\n /** Indicates the field to use to derive which subtype to instantiate; only set on the base meta. */\n stiDiscriminatorField?: string;\n /** The discriminator enum value for this subtype; only set on sub metas. */\n stiDiscriminatorValue?: number;\n /** Whether this type is abstract (cannot be instantiated directly). */\n ctiAbstract?: boolean;\n tagName: string;\n fields: Record<string, Field>;\n /** Physical column names mapped to original storage fields, without CTI base columns. */\n columns: Record<string, { fieldName: string; field: Field }>;\n allFields: Record<string, Field & { aliasSuffix: string; specialized?: true }>;\n /** Usually polys are in `allFields`, but we pull the components out for comp-specific finds, like `parentBook`. */\n polyComponentFields?: Record<string, Field & { aliasSuffix: string }>;\n // Using `any` to avoid type errors between BaseType.metadata & SubType.metadata static fields\n config: ConfigApi<any, any>;\n /** The lazy list of non-read-only reactables for this metadata and its base types. */\n reactables?: Reactable[];\n /** The lazy lookup of non-read-only reactables by source field name. */\n reactablesByField?: ReadonlyMap<string, Reactable[]>;\n /** The lazy list of all reactables for this metadata and its base types. */\n reactablesIncludingReadOnly?: Reactable[];\n /** The lazy lookup of all reactables by source field name. */\n reactablesIncludingReadOnlyByField?: ReadonlyMap<string, Reactable[]>;\n /** The lazy list of reactive validation rules for this metadata and its subtypes. */\n reactiveRules?: ReactiveRule[];\n /** The lazy list of reactive *commit* rules (post-flush/pre-commit) for this metadata and its subtypes. */\n reactiveCommitRules?: ReactiveRule[];\n /** Lazily-cached: whether flushing an entity of this type could trigger any `addCommitRule` work. */\n hasCommitRules?: boolean;\n /** The lazily-cached set of own `lazy` primitive field names, i.e. columns excluded from the default SELECT. */\n lazyFieldNames?: ReadonlySet<string>;\n /** The lazily-cached list of sync-derived (`hasReactiveField`-less) field names, recalced during flush. */\n syncDerivedFields?: string[];\n /** Lazily-cached: whether this entity has any `lazy` columns, i.e. so the default SELECT lists columns explicitly. */\n hasLazyColumns?: boolean;\n orderBy: string | undefined;\n /** Flat field names that can be used to find existing rows before creating. I.e. [[\"email\"], [\"author\", \"title\"]]. */\n uniqueBy?: string[][];\n timestampFields: TimestampFields | undefined;\n // Ideally this would be the application-specific EntityManager (to avoid anys), but it doesn't really matter\n factory: (em: EntityManager<any, any, any>, opts?: any) => DeepNew<T>;\n /** The list of base types for this subtype, e.g. for Dog it'd be [Animal, Mammal]. */\n baseTypes: EntityMetadata[];\n /** The list of subtypes for this base type, e.g. for Animal it'd be `[Mammal, Dog]`. */\n subTypes: EntityMetadata[];\n /** Cached `[...baseTypes, this]` set up by `configureMetadata`, so hot paths avoid re-allocating it. */\n baseSelfMetas?: EntityMetadata[];\n /** Cached `[...baseTypes, this, ...subTypes]` set up by `configureMetadata`, so hot paths avoid re-allocating it. */\n baseSelfAndSubMetas?: EntityMetadata[];\n /** The lazy lookup of subtypes by type name, i.e. `Animal.metadata.subTypesByType.get(\"Dog\")`. */\n subTypesByType?: ReadonlyMap<string, EntityMetadata>;\n /** The lazy lookup of STI subtypes by discriminator value, i.e. `Task.metadata.subTypesByStiValue.get(1)`. */\n subTypesByStiValue?: ReadonlyMap<unknown, EntityMetadata>;\n /** The lazy STI discriminator column name, i.e. `Task.metadata.stiDiscriminatorColumnName`. */\n stiDiscriminatorColumnName?: string;\n /** If the schema is lacking deferred FKs, this is our insertion order. */\n nonDeferredFkOrder?: number;\n}\n\nexport type Field =\n | PrimaryKeyField\n | PrimitiveField\n | EnumField\n | OneToManyField\n | LargeOneToManyField\n | ManyToOneField\n | ManyToManyField\n | ManyToManyEnumField\n | OneToOneField\n | PolymorphicField;\n\n// Only the fields that have defined `serde` keys; should be a mapped type of Field\nexport type SerdeField = PrimaryKeyField | PrimitiveField | EnumField | ManyToOneField | PolymorphicField;\n\nexport type PrimaryKeyField = {\n kind: \"primaryKey\";\n fieldName: string;\n fieldIdName: undefined;\n required: true;\n serde: FieldSerde;\n immutable: true;\n};\n\nexport type PrimitiveField = {\n kind: \"primitive\";\n fieldName: string;\n fieldIdName: undefined;\n required: boolean;\n derived: \"orm\" | \"sync\" | \"async\" | false;\n protected: boolean;\n type: string | Function;\n serde: FieldSerde;\n immutable: boolean;\n citext?: boolean;\n default?: \"schema\" | \"config\";\n sanitize?: boolean;\n /** When true, this column is excluded from the entity's default SELECT and lazy-loaded via a `LazyField`. */\n lazy?: boolean;\n};\n\nexport type EnumField = {\n kind: \"enum\";\n fieldName: string;\n fieldIdName: undefined;\n required: boolean;\n derived: \"sync\" | \"async\" | false;\n enumDetailType: { getValues(): ReadonlyArray<unknown>; findById(id: any): unknown };\n serde: FieldSerde;\n immutable: boolean;\n default?: \"schema\" | \"config\";\n};\n\nexport type OneToManyField = {\n kind: \"o2m\";\n fieldName: string;\n fieldIdName: string;\n required: boolean;\n otherMetadata: () => EntityMetadata;\n otherFieldName: string;\n /** Useful when our other side is a poly, to find which component points back to us. */\n otherColumnName: string;\n serde: undefined;\n immutable: false;\n orderBy?: { field: string; direction: \"ASC\" | \"DESC\" };\n /** When `\"include\"`, this collection's `.get`/`.load` returns soft-deleted entities instead of hiding them. */\n softDeletes?: \"include\" | \"exclude\";\n};\n\nexport type LargeOneToManyField = {\n kind: \"lo2m\";\n fieldName: string;\n fieldIdName: string;\n required: boolean;\n otherMetadata: () => EntityMetadata;\n otherFieldName: string;\n /** Useful when our other side is a poly, to find which component points back to us. */\n otherColumnName: string;\n serde: undefined;\n immutable: false;\n};\n\nexport type ManyToOneField = {\n kind: \"m2o\";\n fieldName: string;\n fieldIdName: string;\n required: boolean;\n otherMetadata: () => EntityMetadata;\n otherFieldName: string;\n serde: FieldSerde;\n immutable: boolean;\n derived: \"async\" | false;\n default?: \"schema\" | \"config\";\n};\n\nexport type ManyToManyField = {\n kind: \"m2m\";\n fieldName: string;\n fieldIdName: string;\n required: boolean;\n derived: \"async\" | \"otherSide\" | false;\n otherMetadata: () => EntityMetadata;\n otherFieldName: string;\n serde: undefined;\n immutable: false;\n joinTableName: string;\n columnNames: [string, string];\n /** Whether the join table has a surrogate `id` PK; if false the FK pair is the composite PK. */\n hasJoinTableId: boolean;\n /** When `\"include\"`, this collection's `.get`/`.load` returns soft-deleted entities instead of hiding them. */\n softDeletes?: \"include\" | \"exclude\";\n};\n\n/**\n * A many-to-many between an entity and an enum \"table\", e.g. `Publisher.logoColors` backed by a\n * `publisher_logo_colors` join table joining `publishers` to the `color` enum table.\n *\n * To the user this looks like an array of enum codes (`Color[]`), but unlike an enum-array column it\n * is lazy (needs a load hint) because it lives in its own join table.\n */\nexport type ManyToManyEnumField = {\n kind: \"m2mEnum\";\n fieldName: string;\n fieldIdName: undefined;\n required: false;\n derived: false;\n /** The enum's metadata, used to map enum codes <-> their numeric ids. */\n enumDetailType: EnumMetadata<any, any, number>;\n serde: undefined;\n immutable: false;\n joinTableName: string;\n /** `[entityColumn, enumColumn]`, e.g. `[\"publisher_id\", \"color_id\"]`. */\n columnNames: [string, string];\n /** Whether the join table has a surrogate `id` PK; if false the FK pair is the composite PK. */\n hasJoinTableId: boolean;\n};\n\nexport type OneToOneField = {\n kind: \"o2o\";\n fieldName: string;\n fieldIdName: string;\n required: boolean;\n otherMetadata: () => EntityMetadata;\n otherFieldName: string;\n /** Useful when our other side is a poly, to find which component points back to us. */\n otherColumnName: string;\n serde: undefined;\n immutable: false;\n};\n\nexport type PolymorphicField = {\n kind: \"poly\";\n fieldName: string; // eg `parent`\n fieldIdName: string; // `parentId`\n required: boolean;\n components: PolymorphicFieldComponent[];\n serde: PolymorphicKeySerde;\n immutable: boolean;\n};\n\nexport type PolymorphicFieldComponent = {\n otherMetadata: () => EntityMetadata;\n otherFieldName: string; // eg `comment` or `comments`\n columnName: string; // eg `parent_book_id` or `parent_book_review_id`\n};\n\nexport function isOneToManyField(ormField: Field): ormField is OneToManyField {\n return ormField.kind === \"o2m\";\n}\n\nexport function isManyToOneField(ormField: Field): ormField is ManyToOneField {\n return ormField.kind === \"m2o\";\n}\n\nexport function isManyToManyField(ormField: Field): ormField is ManyToManyField {\n return ormField.kind === \"m2m\";\n}\n\nexport function isManyToManyEnumField(ormField: Field): ormField is ManyToManyEnumField {\n return ormField.kind === \"m2mEnum\";\n}\n\nexport function isOneToOneField(ormField: Field): ormField is OneToOneField {\n return ormField.kind === \"o2o\";\n}\n\nexport function isPolymorphicField(ormField: Field): ormField is PolymorphicField {\n return ormField.kind === \"poly\";\n}\n\nexport function isReferenceField(ormField: Field): ormField is ManyToOneField | OneToOneField | PolymorphicField {\n return ormField.kind === \"m2o\" || ormField.kind === \"o2o\" || ormField.kind === \"poly\";\n}\n\nexport function isCollectionField(ormField: Field): ormField is OneToManyField | ManyToManyField {\n return ormField.kind === \"o2m\" || ormField.kind === \"m2m\";\n}\n\nexport function getBaseAndSelfMetas(meta: EntityMetadata): EntityMetadata[];\nexport function getBaseAndSelfMetas(entity: Entity): EntityMetadata[];\nexport function getBaseAndSelfMetas(param: Entity | EntityMetadata): EntityMetadata[] {\n return isEntity(param)\n ? getBaseSelfAndSubMetas(getMetadata(param))\n : (param.baseSelfMetas ?? [...param.baseTypes, param]);\n}\n\nexport function getBaseSelfAndSubMetas(meta: EntityMetadata): EntityMetadata[] {\n return meta.baseSelfAndSubMetas ?? [...meta.baseTypes, meta, ...meta.subTypes];\n}\n\nexport function getSubMetas(meta: EntityMetadata): EntityMetadata[] {\n // We should do recursion at some point\n return meta.subTypes;\n}\n\nexport function getBaseMeta(meta: EntityMetadata): EntityMetadata {\n if (!meta.baseType) {\n return meta;\n } else {\n return meta.baseTypes[0];\n }\n}\n"],"mappings":";;;;;;;;;AAYA,SAAgB,YACd,OACgB;CAChB,IAAI,CAAC,OAAO,MAAM,IAAI,MAAM,yBAAyB,OAAO;CAC5D,OACE,OAAO,UAAU,aAAc,MAAc,WAAW,UAAU,QAAQ,QAAQA,mBAAAA,gBAAgB,KAAK,CAAC,CAAC;AAE7G;;AAGA,SAAgB,oBAAoB,MAAsB,WAAmC;CAC3F,IAAI,CAAC,KAAK,UAAU,YAAY,MAAM,IAAI,MAAM,UAAU,UAAU,iBAAiB,KAAK,MAAM;CAGhG,OAAO,EAAE,aAAa,KAAK,WAAW,KAAK,UAAU,UAAU,EAAE,gBAAgB,QAAQ,KAAK,UAC5F,OAAOC,kBAAAA,mBAAmB,KAAK,QAAQ;CAEzC,OAAO;AACT;AA0PA,SAAgB,iBAAiB,UAA6C;CAC5E,OAAO,SAAS,SAAS;AAC3B;AAEA,SAAgB,iBAAiB,UAA6C;CAC5E,OAAO,SAAS,SAAS;AAC3B;AAEA,SAAgB,kBAAkB,UAA8C;CAC9E,OAAO,SAAS,SAAS;AAC3B;AAEA,SAAgB,sBAAsB,UAAkD;CACtF,OAAO,SAAS,SAAS;AAC3B;AAEA,SAAgB,gBAAgB,UAA4C;CAC1E,OAAO,SAAS,SAAS;AAC3B;AAEA,SAAgB,mBAAmB,UAA+C;CAChF,OAAO,SAAS,SAAS;AAC3B;AAEA,SAAgB,iBAAiB,UAAgF;CAC/G,OAAO,SAAS,SAAS,SAAS,SAAS,SAAS,SAAS,SAAS,SAAS;AACjF;AAEA,SAAgB,kBAAkB,UAA+D;CAC/F,OAAO,SAAS,SAAS,SAAS,SAAS,SAAS;AACtD;AAIA,SAAgB,oBAAoB,OAAkD;CACpF,OAAOC,eAAAA,SAAS,KAAK,IACjB,uBAAuB,YAAY,KAAK,CAAC,IACxC,MAAM,iBAAiB,CAAC,GAAG,MAAM,WAAW,KAAK;AACxD;AAEA,SAAgB,uBAAuB,MAAwC;CAC7E,OAAO,KAAK,uBAAuB;EAAC,GAAG,KAAK;EAAW;EAAM,GAAG,KAAK;CAAQ;AAC/E;AAEA,SAAgB,YAAY,MAAwC;CAElE,OAAO,KAAK;AACd;AAEA,SAAgB,YAAY,MAAsC;CAChE,IAAI,CAAC,KAAK,UACR,OAAO;MAEP,OAAO,KAAK,UAAU;AAE1B"}
|
|
@@ -41,9 +41,10 @@ interface EntityMetadata<T extends Entity = any> {
|
|
|
41
41
|
ctiAbstract?: boolean;
|
|
42
42
|
tagName: string;
|
|
43
43
|
fields: Record<string, Field>;
|
|
44
|
-
/** Physical column names mapped to
|
|
44
|
+
/** Physical column names mapped to original storage fields, without CTI base columns. */
|
|
45
45
|
columns: Record<string, {
|
|
46
46
|
fieldName: string;
|
|
47
|
+
field: Field;
|
|
47
48
|
}>;
|
|
48
49
|
allFields: Record<string, Field & {
|
|
49
50
|
aliasSuffix: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EntityMetadata.d.cts","names":[],"sources":["../src/EntityMetadata.ts"],"mappings":";;;;;;;iBASgB,YAAY,UAAU,QAAQ,QAAQ,IAAI,eAAe;iBACzD,YAAY,UAAU,QAAQ,MAAM,+BAA+B,KAAK,eAAe;iBACvF,YAAY,UAAU,QAAQ,MAAM,iBAAiB;;iBAWrD,oBAAoB,MAAM,gBAAgB,oBAAoB;;;;;;;;;;;UAoB7D,eAAe,UAAU;EACxC,MAAM,+BAA+B;EACrC;;EAEA;;EAEA;EACA;;EAEA;;EAEA;EACA;;EAEA;;EAEA;;EAEA;EACA;EACA,QAAQ,eAAe;;EAEvB,SAAS;IAAiB;;
|
|
1
|
+
{"version":3,"file":"EntityMetadata.d.cts","names":[],"sources":["../src/EntityMetadata.ts"],"mappings":";;;;;;;iBASgB,YAAY,UAAU,QAAQ,QAAQ,IAAI,eAAe;iBACzD,YAAY,UAAU,QAAQ,MAAM,+BAA+B,KAAK,eAAe;iBACvF,YAAY,UAAU,QAAQ,MAAM,iBAAiB;;iBAWrD,oBAAoB,MAAM,gBAAgB,oBAAoB;;;;;;;;;;;UAoB7D,eAAe,UAAU;EACxC,MAAM,+BAA+B;EACrC;;EAEA;;EAEA;EACA;;EAEA;;EAEA;EACA;;EAEA;;EAEA;;EAEA;EACA;EACA,QAAQ,eAAe;;EAEvB,SAAS;IAAiB;IAAmB,OAAO;;EACpD,WAAW,eAAe;IAAU;IAAqB;;;EAEzD,sBAAsB,eAAe;IAAU;;EAE/C,QAAQ;;EAER,aAAa;;EAEb,oBAAoB,oBAAoB;;EAExC,8BAA8B;;EAE9B,qCAAqC,oBAAoB;;EAEzD,gBAAgB;;EAEhB,sBAAsB;;EAEtB;;EAEA,iBAAiB;;EAEjB;;EAEA;EACA;;EAEA;EACA,iBAAiB;EAEjB,UAAU,IAAI,8BAA8B,eAAe,QAAQ;;EAEnE,WAAW;;EAEX,UAAU;;EAEV,gBAAgB;;EAEhB,sBAAsB;;EAEtB,iBAAiB,oBAAoB;;EAErC,qBAAqB,qBAAqB;;EAE1C;;EAEA;;KAGU,QACR,kBACA,iBACA,YACA,iBACA,sBACA,iBACA,kBACA,sBACA,gBACA;KAGQ,aAAa,kBAAkB,iBAAiB,YAAY,iBAAiB;KAE7E;EACV;EACA;EACA;EACA;EACA,OAAO;EACP;;KAGU;EACV;EACA;EACA;EACA;EACA;EACA;EACA,eAAe;EACf,OAAO;EACP;EACA;EACA;EACA;;EAEA;;KAGU;EACV;EACA;EACA;EACA;EACA;EACA;IAAkB,aAAa;IAAwB,SAAS;;EAChE,OAAO;EACP;EACA;;KAGU;EACV;EACA;EACA;EACA;EACA,qBAAqB;EACrB;;EAEA;EACA;EACA;EACA;IAAY;IAAe;;;EAE3B;;KAGU;EACV;EACA;EACA;EACA;EACA,qBAAqB;EACrB;;EAEA;EACA;EACA;;KAGU;EACV;EACA;EACA;EACA;EACA,qBAAqB;EACrB;EACA,OAAO;EACP;EACA;EACA;;KAGU;EACV;EACA;EACA;EACA;EACA;EACA,qBAAqB;EACrB;EACA;EACA;EACA;EACA;;EAEA;;EAEA;;;;;;;;;KAUU;EACV;EACA;EACA;EACA;EACA;;EAEA,gBAAgB;EAChB;EACA;EACA;;EAEA;;EAEA;;KAGU;EACV;EACA;EACA;EACA;EACA,qBAAqB;EACrB;;EAEA;EACA;EACA;;KAGU;EACV;EACA;EACA;EACA;EACA,YAAY;EACZ,OAAO;EACP;;KAGU;EACV,qBAAqB;EACrB;EACA;;iBAGc,iBAAiB,UAAU,QAAQ,YAAY;iBAI/C,iBAAiB,UAAU,QAAQ,YAAY;iBAI/C,kBAAkB,UAAU,QAAQ,YAAY;iBAIhD,sBAAsB,UAAU,QAAQ,YAAY;iBAIpD,gBAAgB,UAAU,QAAQ,YAAY;iBAI9C,mBAAmB,UAAU,QAAQ,YAAY;iBAIjD,iBAAiB,UAAU,QAAQ,YAAY,iBAAiB,gBAAgB;iBAIhF,kBAAkB,UAAU,QAAQ,YAAY,iBAAiB;iBAIjE,oBAAoB,MAAM,iBAAiB;iBAC3C,oBAAoB,QAAQ,SAAS;iBAOrC,uBAAuB,MAAM,iBAAiB;iBAI9C,YAAY,MAAM,iBAAiB;iBAKnC,YAAY,MAAM,iBAAiB"}
|
|
@@ -41,9 +41,10 @@ interface EntityMetadata<T extends Entity = any> {
|
|
|
41
41
|
ctiAbstract?: boolean;
|
|
42
42
|
tagName: string;
|
|
43
43
|
fields: Record<string, Field>;
|
|
44
|
-
/** Physical column names mapped to
|
|
44
|
+
/** Physical column names mapped to original storage fields, without CTI base columns. */
|
|
45
45
|
columns: Record<string, {
|
|
46
46
|
fieldName: string;
|
|
47
|
+
field: Field;
|
|
47
48
|
}>;
|
|
48
49
|
allFields: Record<string, Field & {
|
|
49
50
|
aliasSuffix: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EntityMetadata.d.mts","names":[],"sources":["../src/EntityMetadata.ts"],"mappings":";;;;;;;iBASgB,YAAY,UAAU,QAAQ,QAAQ,IAAI,eAAe;iBACzD,YAAY,UAAU,QAAQ,MAAM,+BAA+B,KAAK,eAAe;iBACvF,YAAY,UAAU,QAAQ,MAAM,iBAAiB;;iBAWrD,oBAAoB,MAAM,gBAAgB,oBAAoB;;;;;;;;;;;UAoB7D,eAAe,UAAU;EACxC,MAAM,+BAA+B;EACrC;;EAEA;;EAEA;EACA;;EAEA;;EAEA;EACA;;EAEA;;EAEA;;EAEA;EACA;EACA,QAAQ,eAAe;;EAEvB,SAAS;IAAiB;;
|
|
1
|
+
{"version":3,"file":"EntityMetadata.d.mts","names":[],"sources":["../src/EntityMetadata.ts"],"mappings":";;;;;;;iBASgB,YAAY,UAAU,QAAQ,QAAQ,IAAI,eAAe;iBACzD,YAAY,UAAU,QAAQ,MAAM,+BAA+B,KAAK,eAAe;iBACvF,YAAY,UAAU,QAAQ,MAAM,iBAAiB;;iBAWrD,oBAAoB,MAAM,gBAAgB,oBAAoB;;;;;;;;;;;UAoB7D,eAAe,UAAU;EACxC,MAAM,+BAA+B;EACrC;;EAEA;;EAEA;EACA;;EAEA;;EAEA;EACA;;EAEA;;EAEA;;EAEA;EACA;EACA,QAAQ,eAAe;;EAEvB,SAAS;IAAiB;IAAmB,OAAO;;EACpD,WAAW,eAAe;IAAU;IAAqB;;;EAEzD,sBAAsB,eAAe;IAAU;;EAE/C,QAAQ;;EAER,aAAa;;EAEb,oBAAoB,oBAAoB;;EAExC,8BAA8B;;EAE9B,qCAAqC,oBAAoB;;EAEzD,gBAAgB;;EAEhB,sBAAsB;;EAEtB;;EAEA,iBAAiB;;EAEjB;;EAEA;EACA;;EAEA;EACA,iBAAiB;EAEjB,UAAU,IAAI,8BAA8B,eAAe,QAAQ;;EAEnE,WAAW;;EAEX,UAAU;;EAEV,gBAAgB;;EAEhB,sBAAsB;;EAEtB,iBAAiB,oBAAoB;;EAErC,qBAAqB,qBAAqB;;EAE1C;;EAEA;;KAGU,QACR,kBACA,iBACA,YACA,iBACA,sBACA,iBACA,kBACA,sBACA,gBACA;KAGQ,aAAa,kBAAkB,iBAAiB,YAAY,iBAAiB;KAE7E;EACV;EACA;EACA;EACA;EACA,OAAO;EACP;;KAGU;EACV;EACA;EACA;EACA;EACA;EACA;EACA,eAAe;EACf,OAAO;EACP;EACA;EACA;EACA;;EAEA;;KAGU;EACV;EACA;EACA;EACA;EACA;EACA;IAAkB,aAAa;IAAwB,SAAS;;EAChE,OAAO;EACP;EACA;;KAGU;EACV;EACA;EACA;EACA;EACA,qBAAqB;EACrB;;EAEA;EACA;EACA;EACA;IAAY;IAAe;;;EAE3B;;KAGU;EACV;EACA;EACA;EACA;EACA,qBAAqB;EACrB;;EAEA;EACA;EACA;;KAGU;EACV;EACA;EACA;EACA;EACA,qBAAqB;EACrB;EACA,OAAO;EACP;EACA;EACA;;KAGU;EACV;EACA;EACA;EACA;EACA;EACA,qBAAqB;EACrB;EACA;EACA;EACA;EACA;;EAEA;;EAEA;;;;;;;;;KAUU;EACV;EACA;EACA;EACA;EACA;;EAEA,gBAAgB;EAChB;EACA;EACA;;EAEA;;EAEA;;KAGU;EACV;EACA;EACA;EACA;EACA,qBAAqB;EACrB;;EAEA;EACA;EACA;;KAGU;EACV;EACA;EACA;EACA;EACA,YAAY;EACZ,OAAO;EACP;;KAGU;EACV,qBAAqB;EACrB;EACA;;iBAGc,iBAAiB,UAAU,QAAQ,YAAY;iBAI/C,iBAAiB,UAAU,QAAQ,YAAY;iBAI/C,kBAAkB,UAAU,QAAQ,YAAY;iBAIhD,sBAAsB,UAAU,QAAQ,YAAY;iBAIpD,gBAAgB,UAAU,QAAQ,YAAY;iBAI9C,mBAAmB,UAAU,QAAQ,YAAY;iBAIjD,iBAAiB,UAAU,QAAQ,YAAY,iBAAiB,gBAAgB;iBAIhF,kBAAkB,UAAU,QAAQ,YAAY,iBAAiB;iBAIjE,oBAAoB,MAAM,iBAAiB;iBAC3C,oBAAoB,QAAQ,SAAS;iBAOrC,uBAAuB,MAAM,iBAAiB;iBAI9C,YAAY,MAAM,iBAAiB;iBAKnC,YAAY,MAAM,iBAAiB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EntityMetadata.js","names":[],"sources":["../src/EntityMetadata.ts"],"sourcesContent":["import { getInstanceData } from \"./BaseEntity.ts\";\nimport { type ConfigApi, type Reactable, type ReactiveRule } from \"./config.ts\";\nimport { getMetadataForType } from \"./configure.ts\";\nimport { type Entity, isEntity } from \"./Entity.ts\";\nimport { type EntityManager, type MaybeAbstractEntityConstructor, type TimestampFields } from \"./EntityManager.ts\";\nimport { type EnumMetadata } from \"./EnumMetadata.ts\";\nimport { type DeepNew } from \"./loadHints.ts\";\nimport { type FieldSerde, type PolymorphicKeySerde } from \"./serde.ts\";\n\nexport function getMetadata<T extends Entity>(entity: T): EntityMetadata<T>;\nexport function getMetadata<T extends Entity>(type: MaybeAbstractEntityConstructor<T>): EntityMetadata<T>;\nexport function getMetadata<T extends Entity>(meta: EntityMetadata): EntityMetadata;\nexport function getMetadata<T extends Entity>(\n param: T | MaybeAbstractEntityConstructor<T> | EntityMetadata,\n): EntityMetadata {\n if (!param) throw new Error(`Cannot getMetadata of ${param}`);\n return (\n typeof param === \"function\" ? (param as any).metadata : \"cstr\" in param ? param : getInstanceData(param).metadata\n ) as EntityMetadata;\n}\n\n/** Returns the metadata layer that declares `fieldName`, i.e. `Task.tags` for `TaskOld.tags`. */\nexport function getMetadataForField(meta: EntityMetadata, fieldName: string): EntityMetadata {\n if (!meta.allFields[fieldName]) throw new Error(`Field '${fieldName}' not found on ${meta.type}`);\n // I.e. `TaskOld.tags` is an STI-inherited `Task.tags` and should walk up, but `SmallPublisherGroup.publishers`\n // is a CTI-specialized `PublisherGroup.publishers` and should stay on `SmallPublisherGroup`.\n while (!(fieldName in meta.fields) && meta.allFields[fieldName]?.specialized !== true && meta.baseType) {\n meta = getMetadataForType(meta.baseType);\n }\n return meta;\n}\n\n/**\n * Runtime metadata about an entity.\n *\n * The `joist-codegen` step will generate this by reading the database schema at build/codegen\n * time, along with any customizations in `joist-config.json`.\n *\n * Note: This has no generic, like `T extends Entity`, because `Entity<IdType>` i.e. with\n * an unknown string/id type, causes issues when we want to generically mix `EntityMetadata`\n * of different types, that even liberally using `EntityMetadata<any>` did not avoid.\n */\nexport interface EntityMetadata<T extends Entity = any> {\n cstr: MaybeAbstractEntityConstructor<T>;\n type: string;\n /** How the domain entity's id is represented. */\n idType: \"tagged-string\" | \"untagged-string\" | \"number\";\n /** The database column type, i.e. used to do `::type` casts in Postgres. */\n idDbType: \"bigint\" | \"int\" | \"uuid\" | \"text\";\n tableName: string;\n /** Whether codegen verified this table is a supported SQL mutation target; does not gate reads. */\n supportsEmExecute?: boolean;\n /** If we're a subtype, our immediate base type's name, e.g. for `SmallPublisher` this would be `Publisher`. */\n baseType: string | undefined;\n inheritanceType?: \"sti\" | \"cti\" | undefined;\n /** Indicates the field to use to derive which subtype to instantiate; only set on the base meta. */\n stiDiscriminatorField?: string;\n /** The discriminator enum value for this subtype; only set on sub metas. */\n stiDiscriminatorValue?: number;\n /** Whether this type is abstract (cannot be instantiated directly). */\n ctiAbstract?: boolean;\n tagName: string;\n fields: Record<string, Field>;\n /** Physical column names mapped to their owning domain fields, including inherited fields. */\n columns: Record<string, { fieldName: string }>;\n allFields: Record<string, Field & { aliasSuffix: string; specialized?: true }>;\n /** Usually polys are in `allFields`, but we pull the components out for comp-specific finds, like `parentBook`. */\n polyComponentFields?: Record<string, Field & { aliasSuffix: string }>;\n // Using `any` to avoid type errors between BaseType.metadata & SubType.metadata static fields\n config: ConfigApi<any, any>;\n /** The lazy list of non-read-only reactables for this metadata and its base types. */\n reactables?: Reactable[];\n /** The lazy lookup of non-read-only reactables by source field name. */\n reactablesByField?: ReadonlyMap<string, Reactable[]>;\n /** The lazy list of all reactables for this metadata and its base types. */\n reactablesIncludingReadOnly?: Reactable[];\n /** The lazy lookup of all reactables by source field name. */\n reactablesIncludingReadOnlyByField?: ReadonlyMap<string, Reactable[]>;\n /** The lazy list of reactive validation rules for this metadata and its subtypes. */\n reactiveRules?: ReactiveRule[];\n /** The lazy list of reactive *commit* rules (post-flush/pre-commit) for this metadata and its subtypes. */\n reactiveCommitRules?: ReactiveRule[];\n /** Lazily-cached: whether flushing an entity of this type could trigger any `addCommitRule` work. */\n hasCommitRules?: boolean;\n /** The lazily-cached set of own `lazy` primitive field names, i.e. columns excluded from the default SELECT. */\n lazyFieldNames?: ReadonlySet<string>;\n /** The lazily-cached list of sync-derived (`hasReactiveField`-less) field names, recalced during flush. */\n syncDerivedFields?: string[];\n /** Lazily-cached: whether this entity has any `lazy` columns, i.e. so the default SELECT lists columns explicitly. */\n hasLazyColumns?: boolean;\n orderBy: string | undefined;\n /** Flat field names that can be used to find existing rows before creating. I.e. [[\"email\"], [\"author\", \"title\"]]. */\n uniqueBy?: string[][];\n timestampFields: TimestampFields | undefined;\n // Ideally this would be the application-specific EntityManager (to avoid anys), but it doesn't really matter\n factory: (em: EntityManager<any, any, any>, opts?: any) => DeepNew<T>;\n /** The list of base types for this subtype, e.g. for Dog it'd be [Animal, Mammal]. */\n baseTypes: EntityMetadata[];\n /** The list of subtypes for this base type, e.g. for Animal it'd be `[Mammal, Dog]`. */\n subTypes: EntityMetadata[];\n /** Cached `[...baseTypes, this]` set up by `configureMetadata`, so hot paths avoid re-allocating it. */\n baseSelfMetas?: EntityMetadata[];\n /** Cached `[...baseTypes, this, ...subTypes]` set up by `configureMetadata`, so hot paths avoid re-allocating it. */\n baseSelfAndSubMetas?: EntityMetadata[];\n /** The lazy lookup of subtypes by type name, i.e. `Animal.metadata.subTypesByType.get(\"Dog\")`. */\n subTypesByType?: ReadonlyMap<string, EntityMetadata>;\n /** The lazy lookup of STI subtypes by discriminator value, i.e. `Task.metadata.subTypesByStiValue.get(1)`. */\n subTypesByStiValue?: ReadonlyMap<unknown, EntityMetadata>;\n /** The lazy STI discriminator column name, i.e. `Task.metadata.stiDiscriminatorColumnName`. */\n stiDiscriminatorColumnName?: string;\n /** If the schema is lacking deferred FKs, this is our insertion order. */\n nonDeferredFkOrder?: number;\n}\n\nexport type Field =\n | PrimaryKeyField\n | PrimitiveField\n | EnumField\n | OneToManyField\n | LargeOneToManyField\n | ManyToOneField\n | ManyToManyField\n | ManyToManyEnumField\n | OneToOneField\n | PolymorphicField;\n\n// Only the fields that have defined `serde` keys; should be a mapped type of Field\nexport type SerdeField = PrimaryKeyField | PrimitiveField | EnumField | ManyToOneField | PolymorphicField;\n\nexport type PrimaryKeyField = {\n kind: \"primaryKey\";\n fieldName: string;\n fieldIdName: undefined;\n required: true;\n serde: FieldSerde;\n immutable: true;\n};\n\nexport type PrimitiveField = {\n kind: \"primitive\";\n fieldName: string;\n fieldIdName: undefined;\n required: boolean;\n derived: \"orm\" | \"sync\" | \"async\" | false;\n protected: boolean;\n type: string | Function;\n serde: FieldSerde;\n immutable: boolean;\n citext?: boolean;\n default?: \"schema\" | \"config\";\n sanitize?: boolean;\n /** When true, this column is excluded from the entity's default SELECT and lazy-loaded via a `LazyField`. */\n lazy?: boolean;\n};\n\nexport type EnumField = {\n kind: \"enum\";\n fieldName: string;\n fieldIdName: undefined;\n required: boolean;\n derived: \"sync\" | \"async\" | false;\n enumDetailType: { getValues(): ReadonlyArray<unknown>; findById(id: any): unknown };\n serde: FieldSerde;\n immutable: boolean;\n default?: \"schema\" | \"config\";\n};\n\nexport type OneToManyField = {\n kind: \"o2m\";\n fieldName: string;\n fieldIdName: string;\n required: boolean;\n otherMetadata: () => EntityMetadata;\n otherFieldName: string;\n /** Useful when our other side is a poly, to find which component points back to us. */\n otherColumnName: string;\n serde: undefined;\n immutable: false;\n orderBy?: { field: string; direction: \"ASC\" | \"DESC\" };\n /** When `\"include\"`, this collection's `.get`/`.load` returns soft-deleted entities instead of hiding them. */\n softDeletes?: \"include\" | \"exclude\";\n};\n\nexport type LargeOneToManyField = {\n kind: \"lo2m\";\n fieldName: string;\n fieldIdName: string;\n required: boolean;\n otherMetadata: () => EntityMetadata;\n otherFieldName: string;\n /** Useful when our other side is a poly, to find which component points back to us. */\n otherColumnName: string;\n serde: undefined;\n immutable: false;\n};\n\nexport type ManyToOneField = {\n kind: \"m2o\";\n fieldName: string;\n fieldIdName: string;\n required: boolean;\n otherMetadata: () => EntityMetadata;\n otherFieldName: string;\n serde: FieldSerde;\n immutable: boolean;\n derived: \"async\" | false;\n default?: \"schema\" | \"config\";\n};\n\nexport type ManyToManyField = {\n kind: \"m2m\";\n fieldName: string;\n fieldIdName: string;\n required: boolean;\n derived: \"async\" | \"otherSide\" | false;\n otherMetadata: () => EntityMetadata;\n otherFieldName: string;\n serde: undefined;\n immutable: false;\n joinTableName: string;\n columnNames: [string, string];\n /** Whether the join table has a surrogate `id` PK; if false the FK pair is the composite PK. */\n hasJoinTableId: boolean;\n /** When `\"include\"`, this collection's `.get`/`.load` returns soft-deleted entities instead of hiding them. */\n softDeletes?: \"include\" | \"exclude\";\n};\n\n/**\n * A many-to-many between an entity and an enum \"table\", e.g. `Publisher.logoColors` backed by a\n * `publisher_logo_colors` join table joining `publishers` to the `color` enum table.\n *\n * To the user this looks like an array of enum codes (`Color[]`), but unlike an enum-array column it\n * is lazy (needs a load hint) because it lives in its own join table.\n */\nexport type ManyToManyEnumField = {\n kind: \"m2mEnum\";\n fieldName: string;\n fieldIdName: undefined;\n required: false;\n derived: false;\n /** The enum's metadata, used to map enum codes <-> their numeric ids. */\n enumDetailType: EnumMetadata<any, any, number>;\n serde: undefined;\n immutable: false;\n joinTableName: string;\n /** `[entityColumn, enumColumn]`, e.g. `[\"publisher_id\", \"color_id\"]`. */\n columnNames: [string, string];\n /** Whether the join table has a surrogate `id` PK; if false the FK pair is the composite PK. */\n hasJoinTableId: boolean;\n};\n\nexport type OneToOneField = {\n kind: \"o2o\";\n fieldName: string;\n fieldIdName: string;\n required: boolean;\n otherMetadata: () => EntityMetadata;\n otherFieldName: string;\n /** Useful when our other side is a poly, to find which component points back to us. */\n otherColumnName: string;\n serde: undefined;\n immutable: false;\n};\n\nexport type PolymorphicField = {\n kind: \"poly\";\n fieldName: string; // eg `parent`\n fieldIdName: string; // `parentId`\n required: boolean;\n components: PolymorphicFieldComponent[];\n serde: PolymorphicKeySerde;\n immutable: boolean;\n};\n\nexport type PolymorphicFieldComponent = {\n otherMetadata: () => EntityMetadata;\n otherFieldName: string; // eg `comment` or `comments`\n columnName: string; // eg `parent_book_id` or `parent_book_review_id`\n};\n\nexport function isOneToManyField(ormField: Field): ormField is OneToManyField {\n return ormField.kind === \"o2m\";\n}\n\nexport function isManyToOneField(ormField: Field): ormField is ManyToOneField {\n return ormField.kind === \"m2o\";\n}\n\nexport function isManyToManyField(ormField: Field): ormField is ManyToManyField {\n return ormField.kind === \"m2m\";\n}\n\nexport function isManyToManyEnumField(ormField: Field): ormField is ManyToManyEnumField {\n return ormField.kind === \"m2mEnum\";\n}\n\nexport function isOneToOneField(ormField: Field): ormField is OneToOneField {\n return ormField.kind === \"o2o\";\n}\n\nexport function isPolymorphicField(ormField: Field): ormField is PolymorphicField {\n return ormField.kind === \"poly\";\n}\n\nexport function isReferenceField(ormField: Field): ormField is ManyToOneField | OneToOneField | PolymorphicField {\n return ormField.kind === \"m2o\" || ormField.kind === \"o2o\" || ormField.kind === \"poly\";\n}\n\nexport function isCollectionField(ormField: Field): ormField is OneToManyField | ManyToManyField {\n return ormField.kind === \"o2m\" || ormField.kind === \"m2m\";\n}\n\nexport function getBaseAndSelfMetas(meta: EntityMetadata): EntityMetadata[];\nexport function getBaseAndSelfMetas(entity: Entity): EntityMetadata[];\nexport function getBaseAndSelfMetas(param: Entity | EntityMetadata): EntityMetadata[] {\n return isEntity(param)\n ? getBaseSelfAndSubMetas(getMetadata(param))\n : (param.baseSelfMetas ?? [...param.baseTypes, param]);\n}\n\nexport function getBaseSelfAndSubMetas(meta: EntityMetadata): EntityMetadata[] {\n return meta.baseSelfAndSubMetas ?? [...meta.baseTypes, meta, ...meta.subTypes];\n}\n\nexport function getSubMetas(meta: EntityMetadata): EntityMetadata[] {\n // We should do recursion at some point\n return meta.subTypes;\n}\n\nexport function getBaseMeta(meta: EntityMetadata): EntityMetadata {\n if (!meta.baseType) {\n return meta;\n } else {\n return meta.baseTypes[0];\n }\n}\n"],"mappings":";;;;;;;;AAYA,SAAgB,YACd,OACgB;CAChB,IAAI,CAAC,OAAO,MAAM,IAAI,MAAM,yBAAyB,OAAO;CAC5D,OACE,OAAO,UAAU,aAAc,MAAc,WAAW,UAAU,QAAQ,QAAQ,gBAAgB,KAAK,CAAC,CAAC;AAE7G;;AAGA,SAAgB,oBAAoB,MAAsB,WAAmC;CAC3F,IAAI,CAAC,KAAK,UAAU,YAAY,MAAM,IAAI,MAAM,UAAU,UAAU,iBAAiB,KAAK,MAAM;CAGhG,OAAO,EAAE,aAAa,KAAK,WAAW,KAAK,UAAU,UAAU,EAAE,gBAAgB,QAAQ,KAAK,UAC5F,OAAO,mBAAmB,KAAK,QAAQ;CAEzC,OAAO;AACT;AA0PA,SAAgB,iBAAiB,UAA6C;CAC5E,OAAO,SAAS,SAAS;AAC3B;AAEA,SAAgB,iBAAiB,UAA6C;CAC5E,OAAO,SAAS,SAAS;AAC3B;AAEA,SAAgB,kBAAkB,UAA8C;CAC9E,OAAO,SAAS,SAAS;AAC3B;AAEA,SAAgB,sBAAsB,UAAkD;CACtF,OAAO,SAAS,SAAS;AAC3B;AAEA,SAAgB,gBAAgB,UAA4C;CAC1E,OAAO,SAAS,SAAS;AAC3B;AAEA,SAAgB,mBAAmB,UAA+C;CAChF,OAAO,SAAS,SAAS;AAC3B;AAEA,SAAgB,iBAAiB,UAAgF;CAC/G,OAAO,SAAS,SAAS,SAAS,SAAS,SAAS,SAAS,SAAS,SAAS;AACjF;AAEA,SAAgB,kBAAkB,UAA+D;CAC/F,OAAO,SAAS,SAAS,SAAS,SAAS,SAAS;AACtD;AAIA,SAAgB,oBAAoB,OAAkD;CACpF,OAAO,SAAS,KAAK,IACjB,uBAAuB,YAAY,KAAK,CAAC,IACxC,MAAM,iBAAiB,CAAC,GAAG,MAAM,WAAW,KAAK;AACxD;AAEA,SAAgB,uBAAuB,MAAwC;CAC7E,OAAO,KAAK,uBAAuB;EAAC,GAAG,KAAK;EAAW;EAAM,GAAG,KAAK;CAAQ;AAC/E;AAEA,SAAgB,YAAY,MAAwC;CAElE,OAAO,KAAK;AACd;AAEA,SAAgB,YAAY,MAAsC;CAChE,IAAI,CAAC,KAAK,UACR,OAAO;MAEP,OAAO,KAAK,UAAU;AAE1B"}
|
|
1
|
+
{"version":3,"file":"EntityMetadata.js","names":[],"sources":["../src/EntityMetadata.ts"],"sourcesContent":["import { getInstanceData } from \"./BaseEntity.ts\";\nimport { type ConfigApi, type Reactable, type ReactiveRule } from \"./config.ts\";\nimport { getMetadataForType } from \"./configure.ts\";\nimport { type Entity, isEntity } from \"./Entity.ts\";\nimport { type EntityManager, type MaybeAbstractEntityConstructor, type TimestampFields } from \"./EntityManager.ts\";\nimport { type EnumMetadata } from \"./EnumMetadata.ts\";\nimport { type DeepNew } from \"./loadHints.ts\";\nimport { type FieldSerde, type PolymorphicKeySerde } from \"./serde.ts\";\n\nexport function getMetadata<T extends Entity>(entity: T): EntityMetadata<T>;\nexport function getMetadata<T extends Entity>(type: MaybeAbstractEntityConstructor<T>): EntityMetadata<T>;\nexport function getMetadata<T extends Entity>(meta: EntityMetadata): EntityMetadata;\nexport function getMetadata<T extends Entity>(\n param: T | MaybeAbstractEntityConstructor<T> | EntityMetadata,\n): EntityMetadata {\n if (!param) throw new Error(`Cannot getMetadata of ${param}`);\n return (\n typeof param === \"function\" ? (param as any).metadata : \"cstr\" in param ? param : getInstanceData(param).metadata\n ) as EntityMetadata;\n}\n\n/** Returns the metadata layer that declares `fieldName`, i.e. `Task.tags` for `TaskOld.tags`. */\nexport function getMetadataForField(meta: EntityMetadata, fieldName: string): EntityMetadata {\n if (!meta.allFields[fieldName]) throw new Error(`Field '${fieldName}' not found on ${meta.type}`);\n // I.e. `TaskOld.tags` is an STI-inherited `Task.tags` and should walk up, but `SmallPublisherGroup.publishers`\n // is a CTI-specialized `PublisherGroup.publishers` and should stay on `SmallPublisherGroup`.\n while (!(fieldName in meta.fields) && meta.allFields[fieldName]?.specialized !== true && meta.baseType) {\n meta = getMetadataForType(meta.baseType);\n }\n return meta;\n}\n\n/**\n * Runtime metadata about an entity.\n *\n * The `joist-codegen` step will generate this by reading the database schema at build/codegen\n * time, along with any customizations in `joist-config.json`.\n *\n * Note: This has no generic, like `T extends Entity`, because `Entity<IdType>` i.e. with\n * an unknown string/id type, causes issues when we want to generically mix `EntityMetadata`\n * of different types, that even liberally using `EntityMetadata<any>` did not avoid.\n */\nexport interface EntityMetadata<T extends Entity = any> {\n cstr: MaybeAbstractEntityConstructor<T>;\n type: string;\n /** How the domain entity's id is represented. */\n idType: \"tagged-string\" | \"untagged-string\" | \"number\";\n /** The database column type, i.e. used to do `::type` casts in Postgres. */\n idDbType: \"bigint\" | \"int\" | \"uuid\" | \"text\";\n tableName: string;\n /** Whether codegen verified this table is a supported SQL mutation target; does not gate reads. */\n supportsEmExecute?: boolean;\n /** If we're a subtype, our immediate base type's name, e.g. for `SmallPublisher` this would be `Publisher`. */\n baseType: string | undefined;\n inheritanceType?: \"sti\" | \"cti\" | undefined;\n /** Indicates the field to use to derive which subtype to instantiate; only set on the base meta. */\n stiDiscriminatorField?: string;\n /** The discriminator enum value for this subtype; only set on sub metas. */\n stiDiscriminatorValue?: number;\n /** Whether this type is abstract (cannot be instantiated directly). */\n ctiAbstract?: boolean;\n tagName: string;\n fields: Record<string, Field>;\n /** Physical column names mapped to original storage fields, without CTI base columns. */\n columns: Record<string, { fieldName: string; field: Field }>;\n allFields: Record<string, Field & { aliasSuffix: string; specialized?: true }>;\n /** Usually polys are in `allFields`, but we pull the components out for comp-specific finds, like `parentBook`. */\n polyComponentFields?: Record<string, Field & { aliasSuffix: string }>;\n // Using `any` to avoid type errors between BaseType.metadata & SubType.metadata static fields\n config: ConfigApi<any, any>;\n /** The lazy list of non-read-only reactables for this metadata and its base types. */\n reactables?: Reactable[];\n /** The lazy lookup of non-read-only reactables by source field name. */\n reactablesByField?: ReadonlyMap<string, Reactable[]>;\n /** The lazy list of all reactables for this metadata and its base types. */\n reactablesIncludingReadOnly?: Reactable[];\n /** The lazy lookup of all reactables by source field name. */\n reactablesIncludingReadOnlyByField?: ReadonlyMap<string, Reactable[]>;\n /** The lazy list of reactive validation rules for this metadata and its subtypes. */\n reactiveRules?: ReactiveRule[];\n /** The lazy list of reactive *commit* rules (post-flush/pre-commit) for this metadata and its subtypes. */\n reactiveCommitRules?: ReactiveRule[];\n /** Lazily-cached: whether flushing an entity of this type could trigger any `addCommitRule` work. */\n hasCommitRules?: boolean;\n /** The lazily-cached set of own `lazy` primitive field names, i.e. columns excluded from the default SELECT. */\n lazyFieldNames?: ReadonlySet<string>;\n /** The lazily-cached list of sync-derived (`hasReactiveField`-less) field names, recalced during flush. */\n syncDerivedFields?: string[];\n /** Lazily-cached: whether this entity has any `lazy` columns, i.e. so the default SELECT lists columns explicitly. */\n hasLazyColumns?: boolean;\n orderBy: string | undefined;\n /** Flat field names that can be used to find existing rows before creating. I.e. [[\"email\"], [\"author\", \"title\"]]. */\n uniqueBy?: string[][];\n timestampFields: TimestampFields | undefined;\n // Ideally this would be the application-specific EntityManager (to avoid anys), but it doesn't really matter\n factory: (em: EntityManager<any, any, any>, opts?: any) => DeepNew<T>;\n /** The list of base types for this subtype, e.g. for Dog it'd be [Animal, Mammal]. */\n baseTypes: EntityMetadata[];\n /** The list of subtypes for this base type, e.g. for Animal it'd be `[Mammal, Dog]`. */\n subTypes: EntityMetadata[];\n /** Cached `[...baseTypes, this]` set up by `configureMetadata`, so hot paths avoid re-allocating it. */\n baseSelfMetas?: EntityMetadata[];\n /** Cached `[...baseTypes, this, ...subTypes]` set up by `configureMetadata`, so hot paths avoid re-allocating it. */\n baseSelfAndSubMetas?: EntityMetadata[];\n /** The lazy lookup of subtypes by type name, i.e. `Animal.metadata.subTypesByType.get(\"Dog\")`. */\n subTypesByType?: ReadonlyMap<string, EntityMetadata>;\n /** The lazy lookup of STI subtypes by discriminator value, i.e. `Task.metadata.subTypesByStiValue.get(1)`. */\n subTypesByStiValue?: ReadonlyMap<unknown, EntityMetadata>;\n /** The lazy STI discriminator column name, i.e. `Task.metadata.stiDiscriminatorColumnName`. */\n stiDiscriminatorColumnName?: string;\n /** If the schema is lacking deferred FKs, this is our insertion order. */\n nonDeferredFkOrder?: number;\n}\n\nexport type Field =\n | PrimaryKeyField\n | PrimitiveField\n | EnumField\n | OneToManyField\n | LargeOneToManyField\n | ManyToOneField\n | ManyToManyField\n | ManyToManyEnumField\n | OneToOneField\n | PolymorphicField;\n\n// Only the fields that have defined `serde` keys; should be a mapped type of Field\nexport type SerdeField = PrimaryKeyField | PrimitiveField | EnumField | ManyToOneField | PolymorphicField;\n\nexport type PrimaryKeyField = {\n kind: \"primaryKey\";\n fieldName: string;\n fieldIdName: undefined;\n required: true;\n serde: FieldSerde;\n immutable: true;\n};\n\nexport type PrimitiveField = {\n kind: \"primitive\";\n fieldName: string;\n fieldIdName: undefined;\n required: boolean;\n derived: \"orm\" | \"sync\" | \"async\" | false;\n protected: boolean;\n type: string | Function;\n serde: FieldSerde;\n immutable: boolean;\n citext?: boolean;\n default?: \"schema\" | \"config\";\n sanitize?: boolean;\n /** When true, this column is excluded from the entity's default SELECT and lazy-loaded via a `LazyField`. */\n lazy?: boolean;\n};\n\nexport type EnumField = {\n kind: \"enum\";\n fieldName: string;\n fieldIdName: undefined;\n required: boolean;\n derived: \"sync\" | \"async\" | false;\n enumDetailType: { getValues(): ReadonlyArray<unknown>; findById(id: any): unknown };\n serde: FieldSerde;\n immutable: boolean;\n default?: \"schema\" | \"config\";\n};\n\nexport type OneToManyField = {\n kind: \"o2m\";\n fieldName: string;\n fieldIdName: string;\n required: boolean;\n otherMetadata: () => EntityMetadata;\n otherFieldName: string;\n /** Useful when our other side is a poly, to find which component points back to us. */\n otherColumnName: string;\n serde: undefined;\n immutable: false;\n orderBy?: { field: string; direction: \"ASC\" | \"DESC\" };\n /** When `\"include\"`, this collection's `.get`/`.load` returns soft-deleted entities instead of hiding them. */\n softDeletes?: \"include\" | \"exclude\";\n};\n\nexport type LargeOneToManyField = {\n kind: \"lo2m\";\n fieldName: string;\n fieldIdName: string;\n required: boolean;\n otherMetadata: () => EntityMetadata;\n otherFieldName: string;\n /** Useful when our other side is a poly, to find which component points back to us. */\n otherColumnName: string;\n serde: undefined;\n immutable: false;\n};\n\nexport type ManyToOneField = {\n kind: \"m2o\";\n fieldName: string;\n fieldIdName: string;\n required: boolean;\n otherMetadata: () => EntityMetadata;\n otherFieldName: string;\n serde: FieldSerde;\n immutable: boolean;\n derived: \"async\" | false;\n default?: \"schema\" | \"config\";\n};\n\nexport type ManyToManyField = {\n kind: \"m2m\";\n fieldName: string;\n fieldIdName: string;\n required: boolean;\n derived: \"async\" | \"otherSide\" | false;\n otherMetadata: () => EntityMetadata;\n otherFieldName: string;\n serde: undefined;\n immutable: false;\n joinTableName: string;\n columnNames: [string, string];\n /** Whether the join table has a surrogate `id` PK; if false the FK pair is the composite PK. */\n hasJoinTableId: boolean;\n /** When `\"include\"`, this collection's `.get`/`.load` returns soft-deleted entities instead of hiding them. */\n softDeletes?: \"include\" | \"exclude\";\n};\n\n/**\n * A many-to-many between an entity and an enum \"table\", e.g. `Publisher.logoColors` backed by a\n * `publisher_logo_colors` join table joining `publishers` to the `color` enum table.\n *\n * To the user this looks like an array of enum codes (`Color[]`), but unlike an enum-array column it\n * is lazy (needs a load hint) because it lives in its own join table.\n */\nexport type ManyToManyEnumField = {\n kind: \"m2mEnum\";\n fieldName: string;\n fieldIdName: undefined;\n required: false;\n derived: false;\n /** The enum's metadata, used to map enum codes <-> their numeric ids. */\n enumDetailType: EnumMetadata<any, any, number>;\n serde: undefined;\n immutable: false;\n joinTableName: string;\n /** `[entityColumn, enumColumn]`, e.g. `[\"publisher_id\", \"color_id\"]`. */\n columnNames: [string, string];\n /** Whether the join table has a surrogate `id` PK; if false the FK pair is the composite PK. */\n hasJoinTableId: boolean;\n};\n\nexport type OneToOneField = {\n kind: \"o2o\";\n fieldName: string;\n fieldIdName: string;\n required: boolean;\n otherMetadata: () => EntityMetadata;\n otherFieldName: string;\n /** Useful when our other side is a poly, to find which component points back to us. */\n otherColumnName: string;\n serde: undefined;\n immutable: false;\n};\n\nexport type PolymorphicField = {\n kind: \"poly\";\n fieldName: string; // eg `parent`\n fieldIdName: string; // `parentId`\n required: boolean;\n components: PolymorphicFieldComponent[];\n serde: PolymorphicKeySerde;\n immutable: boolean;\n};\n\nexport type PolymorphicFieldComponent = {\n otherMetadata: () => EntityMetadata;\n otherFieldName: string; // eg `comment` or `comments`\n columnName: string; // eg `parent_book_id` or `parent_book_review_id`\n};\n\nexport function isOneToManyField(ormField: Field): ormField is OneToManyField {\n return ormField.kind === \"o2m\";\n}\n\nexport function isManyToOneField(ormField: Field): ormField is ManyToOneField {\n return ormField.kind === \"m2o\";\n}\n\nexport function isManyToManyField(ormField: Field): ormField is ManyToManyField {\n return ormField.kind === \"m2m\";\n}\n\nexport function isManyToManyEnumField(ormField: Field): ormField is ManyToManyEnumField {\n return ormField.kind === \"m2mEnum\";\n}\n\nexport function isOneToOneField(ormField: Field): ormField is OneToOneField {\n return ormField.kind === \"o2o\";\n}\n\nexport function isPolymorphicField(ormField: Field): ormField is PolymorphicField {\n return ormField.kind === \"poly\";\n}\n\nexport function isReferenceField(ormField: Field): ormField is ManyToOneField | OneToOneField | PolymorphicField {\n return ormField.kind === \"m2o\" || ormField.kind === \"o2o\" || ormField.kind === \"poly\";\n}\n\nexport function isCollectionField(ormField: Field): ormField is OneToManyField | ManyToManyField {\n return ormField.kind === \"o2m\" || ormField.kind === \"m2m\";\n}\n\nexport function getBaseAndSelfMetas(meta: EntityMetadata): EntityMetadata[];\nexport function getBaseAndSelfMetas(entity: Entity): EntityMetadata[];\nexport function getBaseAndSelfMetas(param: Entity | EntityMetadata): EntityMetadata[] {\n return isEntity(param)\n ? getBaseSelfAndSubMetas(getMetadata(param))\n : (param.baseSelfMetas ?? [...param.baseTypes, param]);\n}\n\nexport function getBaseSelfAndSubMetas(meta: EntityMetadata): EntityMetadata[] {\n return meta.baseSelfAndSubMetas ?? [...meta.baseTypes, meta, ...meta.subTypes];\n}\n\nexport function getSubMetas(meta: EntityMetadata): EntityMetadata[] {\n // We should do recursion at some point\n return meta.subTypes;\n}\n\nexport function getBaseMeta(meta: EntityMetadata): EntityMetadata {\n if (!meta.baseType) {\n return meta;\n } else {\n return meta.baseTypes[0];\n }\n}\n"],"mappings":";;;;;;;;AAYA,SAAgB,YACd,OACgB;CAChB,IAAI,CAAC,OAAO,MAAM,IAAI,MAAM,yBAAyB,OAAO;CAC5D,OACE,OAAO,UAAU,aAAc,MAAc,WAAW,UAAU,QAAQ,QAAQ,gBAAgB,KAAK,CAAC,CAAC;AAE7G;;AAGA,SAAgB,oBAAoB,MAAsB,WAAmC;CAC3F,IAAI,CAAC,KAAK,UAAU,YAAY,MAAM,IAAI,MAAM,UAAU,UAAU,iBAAiB,KAAK,MAAM;CAGhG,OAAO,EAAE,aAAa,KAAK,WAAW,KAAK,UAAU,UAAU,EAAE,gBAAgB,QAAQ,KAAK,UAC5F,OAAO,mBAAmB,KAAK,QAAQ;CAEzC,OAAO;AACT;AA0PA,SAAgB,iBAAiB,UAA6C;CAC5E,OAAO,SAAS,SAAS;AAC3B;AAEA,SAAgB,iBAAiB,UAA6C;CAC5E,OAAO,SAAS,SAAS;AAC3B;AAEA,SAAgB,kBAAkB,UAA8C;CAC9E,OAAO,SAAS,SAAS;AAC3B;AAEA,SAAgB,sBAAsB,UAAkD;CACtF,OAAO,SAAS,SAAS;AAC3B;AAEA,SAAgB,gBAAgB,UAA4C;CAC1E,OAAO,SAAS,SAAS;AAC3B;AAEA,SAAgB,mBAAmB,UAA+C;CAChF,OAAO,SAAS,SAAS;AAC3B;AAEA,SAAgB,iBAAiB,UAAgF;CAC/G,OAAO,SAAS,SAAS,SAAS,SAAS,SAAS,SAAS,SAAS,SAAS;AACjF;AAEA,SAAgB,kBAAkB,UAA+D;CAC/F,OAAO,SAAS,SAAS,SAAS,SAAS,SAAS;AACtD;AAIA,SAAgB,oBAAoB,OAAkD;CACpF,OAAO,SAAS,KAAK,IACjB,uBAAuB,YAAY,KAAK,CAAC,IACxC,MAAM,iBAAiB,CAAC,GAAG,MAAM,WAAW,KAAK;AACxD;AAEA,SAAgB,uBAAuB,MAAwC;CAC7E,OAAO,KAAK,uBAAuB;EAAC,GAAG,KAAK;EAAW;EAAM,GAAG,KAAK;CAAQ;AAC/E;AAEA,SAAgB,YAAY,MAAwC;CAElE,OAAO,KAAK;AACd;AAEA,SAAgB,YAAY,MAAsC;CAChE,IAAI,CAAC,KAAK,UACR,OAAO;MAEP,OAAO,KAAK,UAAU;AAE1B"}
|
package/build/Expr.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_keywords = require("./keywords.cjs");
|
|
3
|
-
const
|
|
3
|
+
const require_skipCondition = require("./skipCondition.cjs");
|
|
4
4
|
//#region src/Expr.ts
|
|
5
5
|
/**
|
|
6
6
|
* The shared expression protocol for `em.query`.
|
|
@@ -52,7 +52,6 @@ function deferredCondition(fn) {
|
|
|
52
52
|
function resolveDeferredConditions(cond, ctx) {
|
|
53
53
|
if (cond === void 0 || cond === null) return cond;
|
|
54
54
|
if (isDeferredCondition(cond)) return cond[deferredSym](ctx);
|
|
55
|
-
else if (require_DeferredAlias.isDeferredAliasCondition(cond)) return cond[require_DeferredAlias.deferredAliasSym](ctxResolver(ctx));
|
|
56
55
|
else if ("and" in cond && cond.and) return {
|
|
57
56
|
...cond,
|
|
58
57
|
and: cond.and.map((c) => resolveDeferredConditions(c, ctx))
|
|
@@ -74,8 +73,8 @@ function joinFragments(parts, sep) {
|
|
|
74
73
|
/**
|
|
75
74
|
* The methods every expression shares. Subclasses provide `toSql`, and usually `decode`/`encode`.
|
|
76
75
|
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
76
|
+
* Table columns can override comparisons to apply column-specific conversions. Domain aliases do not
|
|
77
|
+
* implement this protocol.
|
|
79
78
|
*/
|
|
80
79
|
var BaseExpr = class {
|
|
81
80
|
[exprBrand] = this;
|
|
@@ -196,7 +195,7 @@ var BaseExpr = class {
|
|
|
196
195
|
}
|
|
197
196
|
/** `this op value`, where `value` may be `undefined` (pruned), `null`, another expression, or a literal. */
|
|
198
197
|
compare(op, value) {
|
|
199
|
-
if (value === void 0) return
|
|
198
|
+
if (value === void 0) return require_skipCondition.skipCondition;
|
|
200
199
|
if (value === null) {
|
|
201
200
|
const not = op === "=" ? "" : op === "!=" ? "NOT " : fail(`Cannot compare ${op} to null`);
|
|
202
201
|
return deferredCondition((ctx) => {
|
|
@@ -219,7 +218,7 @@ var BaseExpr = class {
|
|
|
219
218
|
}
|
|
220
219
|
/** `this IN (subquery)` or `this = ANY(?)` for a list; `NOT IN` / `!= ALL(?)` for `nin`. */
|
|
221
220
|
inList(op, values) {
|
|
222
|
-
if (values === void 0) return
|
|
221
|
+
if (values === void 0) return require_skipCondition.skipCondition;
|
|
223
222
|
if (isExpr(values)) return deferredCondition((ctx) => {
|
|
224
223
|
const left = this.toSql(ctx);
|
|
225
224
|
const right = asNode(values).toSqlBare(ctx);
|
|
@@ -330,7 +329,7 @@ var RefExpr = class extends BaseExpr {
|
|
|
330
329
|
/**
|
|
331
330
|
* A `sql` tagged template.
|
|
332
331
|
*
|
|
333
|
-
* For an Author
|
|
332
|
+
* For an Author table `a` assigned the SQL alias `a1`:
|
|
334
333
|
*
|
|
335
334
|
* ```ts
|
|
336
335
|
* sql`${a.age} * 2` // Expression: a1.age * 2
|
|
@@ -512,15 +511,6 @@ function minMaxOutputType(outputType) {
|
|
|
512
511
|
default: return;
|
|
513
512
|
}
|
|
514
513
|
}
|
|
515
|
-
/** An `AliasResolver` backed by an `ExprContext`; a handle's bound meta is its own (`em.query` sources are their own tables). */
|
|
516
|
-
function ctxResolver(ctx) {
|
|
517
|
-
return function resolve(handle) {
|
|
518
|
-
return {
|
|
519
|
-
meta: handle.meta,
|
|
520
|
-
alias: ctx.aliasFor(handle)
|
|
521
|
-
};
|
|
522
|
-
};
|
|
523
|
-
}
|
|
524
514
|
/** Decodes `count`/`sum`/`avg` results, which Postgres returns as strings for bigint/numeric. */
|
|
525
515
|
function decodeNumber(value) {
|
|
526
516
|
return typeof value === "string" ? Number(value) : value;
|
|
@@ -540,18 +530,14 @@ exports.TemplateExpr = TemplateExpr;
|
|
|
540
530
|
exports.arrayOutputType = arrayOutputType;
|
|
541
531
|
exports.asNode = asNode;
|
|
542
532
|
exports.canonicalDbType = canonicalDbType;
|
|
543
|
-
exports.deferredAliasSym = require_DeferredAlias.deferredAliasSym;
|
|
544
533
|
exports.deferredCondition = deferredCondition;
|
|
545
534
|
exports.deferredSym = deferredSym;
|
|
546
535
|
exports.exprBrand = exprBrand;
|
|
547
536
|
exports.interpolationToSql = interpolationToSql;
|
|
548
537
|
exports.isConditionLike = isConditionLike;
|
|
549
|
-
exports.isDeferredAliasCondition = require_DeferredAlias.isDeferredAliasCondition;
|
|
550
538
|
exports.isDeferredCondition = isDeferredCondition;
|
|
551
539
|
exports.isExpr = isExpr;
|
|
552
540
|
exports.joinFragments = joinFragments;
|
|
553
541
|
exports.resolveDeferredConditions = resolveDeferredConditions;
|
|
554
|
-
exports.skipCondition = require_DeferredAlias.skipCondition;
|
|
555
|
-
exports.withDeferredAlias = require_DeferredAlias.withDeferredAlias;
|
|
556
542
|
|
|
557
543
|
//# sourceMappingURL=Expr.cjs.map
|
package/build/Expr.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Expr.cjs","names":["isDeferredAliasCondition","deferredAliasSym","skipCondition","safeKq"],"sources":["../src/Expr.ts"],"sourcesContent":["import { type AliasResolver, deferredAliasSym, isDeferredAliasCondition, skipCondition } from \"./DeferredAlias.ts\";\nexport {\n type AliasResolver,\n type DeferredAliasCondition,\n deferredAliasSym,\n isDeferredAliasCondition,\n skipCondition,\n withDeferredAlias,\n} from \"./DeferredAlias.ts\";\nimport type { ExpressionCondition } from \"./EntityFilter.ts\";\nimport type { EntityMetadata } from \"./EntityMetadata.ts\";\nimport { safeKq } from \"./keywords.ts\";\nimport type { RawCondition } from \"./QueryParser.ts\";\n\n/**\n * The shared expression protocol for `em.query`.\n *\n * Table columns (`a.first_name`), aggregates (`b.id.count()`), `sql` templates, subquery columns\n * (`bookStats.bookCount`), and scalar subqueries all implement it, so any of them can appear in\n * `select`, `where`, `groupBy`, `having`, `orderBy`, and inside other expressions.\n *\n * This module is a leaf on purpose: `Tables.ts` extends `BaseExpr` at load time, so nothing here may\n * import (at runtime) a module that leads back to `Tables.ts`. Anything that needs metadata, alias\n * binding, or SQL generation for conditions is reached through the `ExprContext` the query parser passes in.\n */\n\nexport const exprBrand: unique symbol = Symbol(\"joist.expr\");\n\n/**\n * Phantom type information carried by every `Expr`.\n *\n * `R` is the decoded result type.\n *\n * `Src` is the expression's *source key*: the type-level identity of the table it reads from. An entity\n * table's key is its type name (`table(Author)` gives `\"Author\"`) or the explicit name in\n * `table(Author, \"m\")`; a subquery's key is its `as: \"book_stats\"`, or the shared sentinel `\"?\"` when it\n * has no `as`. Exactly two questions are asked of a source key, and nothing else:\n *\n * - `MaybeNull` asks \"is my source key among the LEFT-joined sources in this query's join list?\" If yes,\n * the value can be `null`.\n * - `CheckScope` asks \"is my source key among `from` + `join` at all?\" If no, the query reads from a\n * table it never joined.\n *\n * Two special keys opt out of both questions:\n *\n * - `Expr<number, never>` reads from nothing that can be left-joined away, i.e. `b.id.count()`\n * - `Expr<number, string>` (the default) is untracked, i.e. `sql.ref` on an unknown table\n */\nexport interface ExprBrand<R, Src extends string> {\n readonly __result: R;\n readonly __source: Src;\n}\n\n/**\n * Runtime type information that lets set operations reject outputs that cannot safely share conversions.\n *\n * PostgreSQL compares projected SQL rows and chooses a common SQL type before Joist decodes them.\n * A compound uses one decoder per output column and retains an encoder for later comparisons and\n * coalesce fallbacks. We must check every operand before reusing the first operand's conversions:\n * TypeScript result types are erased, and equal SQL storage types do not imply equal logical domains.\n *\n * I.e. Author.id and Book.author_id are compatible Author IDs despite having separate serdes, while\n * Book.id must not compare or decode as an Author ID just because both use int4. Conversely, Author.age\n * and Author.age.sum() both expose TypeScript numbers but produce int4 and int8 driver values. Without\n * their SQL type information, accepting them could make decoding depend on which operand comes first.\n * Enum, custom-mapper, and JSON-schema domain tokens also prevent unrelated conversions from mixing.\n *\n * Compatibility requires exact equality of the canonical SQL `dbType`, the `domain` token, and\n * `idMeta`. An absent descriptor is unknown, not an identity codec. `idMeta` identifies scalar IDs only;\n * arrays retain their element domain separately so they cannot select a polymorphic IN component.\n * Raw `sql<R>` annotations do not supply this runtime information. Encoding and decoding remain on the\n * expression; this descriptor only holds the information needed for these conservative checks.\n */\nexport interface ExprOutputType {\n dbType: string;\n domain: unknown;\n idMeta?: EntityMetadata;\n /** Internal conversion proof, consistent for equal dbType/domain/idMeta; not another compatibility key. */\n arrayElementSafe?: boolean;\n}\n\n/**\n * \"Any expression whose result is `R`\", checked by brand alone.\n *\n * Method parameters use this instead of `Expr<R>` so that `Expr` stays covariant in `R`: checking\n * `Expr<AuthorId>` against `Expr<AuthorId | null>` then only compares the phantom `__result`, not every\n * method's parameter list (which would make `Expr` invariant, and break `select: b.author_id` dispatch and\n * polymorphic joins).\n */\nexport type ExprLike<R> = { readonly [exprBrand]: ExprBrand<R, any> };\n\n/**\n * A typed SQL expression: a table column, an aggregate, a `sql` template, or a scalar subquery.\n *\n * Conditions and SQL functions are methods, so they need no import. Aggregates keep `Src` so scope\n * checking still sees through `bs.x.max()`; `count` is source-less because `count(x)` is 0, not null,\n * when `x`'s table is left-joined away; `coalesce` drops `Src` on purpose, since its whole job is to\n * remove the nullability a left join adds.\n */\nexport interface Expr<R, Src extends string = string> {\n readonly [exprBrand]: ExprBrand<R, Src>;\n eq(value: R | ExprLike<R> | undefined): ExpressionCondition;\n ne(value: R | ExprLike<R> | undefined): ExpressionCondition;\n gt(value: R | ExprLike<R> | undefined): ExpressionCondition;\n gte(value: R | ExprLike<R> | undefined): ExpressionCondition;\n lt(value: R | ExprLike<R> | undefined): ExpressionCondition;\n lte(value: R | ExprLike<R> | undefined): ExpressionCondition;\n // A list subquery may select a nullable column; NULLs in the set never match, so that is fine.\n in(values: readonly R[] | ExprLike<R | null> | undefined): ExpressionCondition;\n nin(values: readonly R[] | ExprLike<R | null> | undefined): ExpressionCondition;\n\n /** `count(x)::int`; `count(a.id)` is `count(*)` for the FROM table, and the matched-row count for a left-joined one. */\n count(): Expr<number, never>;\n countDistinct(): Expr<number, never>;\n sum(this: Expr<number | null, Src>): Expr<number | null, Src>;\n avg(this: Expr<number | null, Src>): Expr<number | null, Src>;\n min(): Expr<R | null, Src>;\n max(): Expr<R | null, Src>;\n /** PG keeps element NULLs (a left-joined empty group aggregates as `[null]`), and zero rows aggregate as NULL. */\n arrayAgg(): Expr<R[] | null, Src>;\n stringAgg(this: Expr<string | null, Src>, delimiter: string): Expr<string | null, Src>;\n coalesce(fallback: NonNullable<R>): Expr<NonNullable<R>, never>;\n}\n\n/**\n * A join entry: the join kind is the key, the joined source is the value, plus `on`. `inner?: never` /\n * `left?: never` keep an entry to one kind (the `ExpressionFilter` `and`/`or` trick).\n *\n * `on` is required. A join is pruned when nothing references it anymore, not by an `undefined` ON;\n * `keep: true` pins a join that would otherwise prune, i.e. an inner join used as an existence filter,\n * the way em.find's `keepAliases` does. It is a boolean so callers can pass a flag.\n *\n * Declared here (not `query.ts`) so the relation join factories in `Tables.ts` (i.e. `a.books.as(b)`) can\n * return them without importing `query.ts`; `query.ts` re-constrains `A` to its `QuerySource`.\n */\nexport interface InnerJoin<A> {\n readonly inner: A;\n readonly left?: never;\n readonly on: ExpressionCondition;\n readonly keep?: boolean;\n}\n\nexport interface LeftJoin<A> {\n readonly left: A;\n readonly inner?: never;\n readonly on: ExpressionCondition;\n readonly keep?: boolean;\n}\n\n/** SQL plus its `?` bindings plus the SQL aliases it references, i.e. for join pruning. */\nexport interface SqlFragment {\n sql: string;\n bindings: any[];\n refs: string[];\n}\n\n/**\n * What an expression needs from the query it is generating SQL for: the SQL alias assigned to each\n * source, and a way to turn nested conditions into SQL (which needs `ConditionBuilder`, so it lives in `query.ts`).\n */\nexport interface ExprContext {\n /** Returns the SQL alias for an alias's `AliasMgmt` or a subquery handle, searching enclosing queries. */\n aliasFor(handle: object): string;\n /** Turns a user-facing condition into SQL; `undefined` if it pruned away entirely. */\n conditionToSql(cond: ExpressionCondition): SqlFragment | undefined;\n}\n\nexport function isExpr(value: unknown): value is ExprLike<any> {\n return typeof value === \"object\" && value !== null && exprBrand in value;\n}\n\n/** Every `Expr` is a `BaseExpr` at runtime; this cast keeps `isExpr` a plain type guard so unions narrow. */\nexport function asNode(expr: ExprLike<any>): BaseExpr {\n return expr as any as BaseExpr;\n}\n\nexport const deferredSym: unique symbol = Symbol(\"joist.deferredCondition\");\n\n/**\n * A condition whose SQL depends on aliases that are only known once the query is parsed, i.e.\n * `bookStats.bookCount.gt(1)` or `bs.authorId.eq(a.id)`.\n *\n * It is shaped like a `RawCondition` so it can sit in any `ExpressionFilter`; `resolveDeferredConditions`\n * snapshots `condition`, `bindings`, and `aliases` before the filter is parsed. Alias columns compared to\n * literals keep producing `ColumnCondition`s (the `em.find` path), so this is only for comparisons that\n * involve a non-alias expression.\n */\nexport interface DeferredCondition extends RawCondition {\n [deferredSym]: (ctx: ExprContext) => RawCondition;\n}\n\nexport function isDeferredCondition(cond: unknown): cond is DeferredCondition {\n return typeof cond === \"object\" && cond !== null && deferredSym in cond;\n}\n\n/** Creates a `DeferredCondition` that generates its SQL with `fn` once the query's aliases are known. */\nexport function deferredCondition(fn: (ctx: ExprContext) => SqlFragment): DeferredCondition {\n const cond: DeferredCondition = {\n kind: \"raw\",\n aliases: [],\n condition: \"<unresolved>\",\n bindings: [],\n pruneable: false,\n [deferredSym]: (ctx) => {\n const { sql, bindings, refs } = fn(ctx);\n return { ...cond, condition: sql, bindings, aliases: refs };\n },\n };\n return cond;\n}\n\n/**\n * Resolve each condition occurrence and snapshot it before resolving the next one. A nested subquery\n * may reuse the same condition under another alias; it must not overwrite this occurrence's SQL.\n */\nexport function resolveDeferredConditions(\n cond: ExpressionCondition | undefined,\n ctx: ExprContext,\n): ExpressionCondition | undefined {\n if (cond === undefined || cond === null) return cond;\n if (isDeferredCondition(cond)) {\n return cond[deferredSym](ctx);\n } else if (isDeferredAliasCondition(cond)) {\n return cond[deferredAliasSym](ctxResolver(ctx));\n } else if (\"and\" in cond && cond.and) {\n return { ...cond, and: cond.and.map((c) => resolveDeferredConditions(c, ctx)) };\n } else if (\"or\" in cond && cond.or) {\n return { ...cond, or: cond.or.map((c) => resolveDeferredConditions(c, ctx)) };\n }\n return cond;\n}\n\n/** Concatenates SQL fragments with `sep`, keeping bindings and refs in order. */\nexport function joinFragments(parts: SqlFragment[], sep: string): SqlFragment {\n return {\n sql: parts.map((p) => p.sql).join(sep),\n bindings: parts.flatMap((p) => p.bindings),\n refs: parts.flatMap((p) => p.refs),\n };\n}\n\n/**\n * The methods every expression shares. Subclasses provide `toSql`, and usually `decode`/`encode`.\n *\n * Alias columns override the comparison methods with the `em.find` `ColumnCondition` path when the\n * right-hand side is a literal, and fall back to these for expression-vs-expression comparisons.\n */\nexport abstract class BaseExpr {\n readonly [exprBrand]: any = this;\n\n /** Produces this expression's SQL so it can be embedded in a larger expression, i.e. a subquery gets parens. */\n abstract toSql(ctx: ExprContext): SqlFragment;\n\n /** Only an actual scalar/IN subquery exposes its selected expression, not an ordinary ID expression. */\n get subquerySelect(): BaseExpr | undefined {\n return undefined;\n }\n\n /** Known SQL representation and logical domain; raw SQL and unmodeled refs remain unknown. */\n get outputType(): ExprOutputType | undefined {\n return undefined;\n }\n\n /** Physical SQL nullability; undefined means unknown, not a NOT NULL guarantee. */\n get sqlNullable(): boolean | undefined {\n return undefined;\n }\n\n /** The source of a direct column reference, whose value becomes NULL under an unmatched LEFT join. */\n get sqlSource(): object | undefined {\n return undefined;\n }\n\n /** Produces the SQL without the outer parens a subquery normally gets; only differs for subqueries. */\n toSqlBare(ctx: ExprContext): SqlFragment {\n return this.toSql(ctx);\n }\n\n /** Converts a result-set value into the domain value, i.e. an int into a tagged id. */\n decode(value: unknown): unknown {\n return value;\n }\n\n /** Converts a domain value into the database value, i.e. a tagged id into an int, for bindings. */\n encode(value: unknown): unknown {\n return value;\n }\n\n eq(value: unknown): ExpressionCondition {\n return this.compare(\"=\", value);\n }\n\n ne(value: unknown): ExpressionCondition {\n return this.compare(\"!=\", value);\n }\n\n gt(value: unknown): ExpressionCondition {\n return this.compare(\">\", value);\n }\n\n gte(value: unknown): ExpressionCondition {\n return this.compare(\">=\", value);\n }\n\n lt(value: unknown): ExpressionCondition {\n return this.compare(\"<\", value);\n }\n\n lte(value: unknown): ExpressionCondition {\n return this.compare(\"<=\", value);\n }\n\n in(values: unknown): ExpressionCondition {\n return this.inList(\"IN\", values);\n }\n\n nin(values: unknown): ExpressionCondition {\n return this.inList(\"NOT IN\", values);\n }\n\n count(): Expr<number, never> {\n return new FnExpr(\"count\", [this], {\n suffix: \"::int\",\n decode: decodeNumber,\n encode: identity,\n outputType: { dbType: \"int4\", domain: Number, arrayElementSafe: true },\n }) as any;\n }\n\n countDistinct(): Expr<number, never> {\n return new FnExpr(\"count\", [this], {\n prefix: \"DISTINCT \",\n suffix: \"::int\",\n decode: decodeNumber,\n encode: identity,\n outputType: { dbType: \"int4\", domain: Number, arrayElementSafe: true },\n }) as any;\n }\n\n sum(): Expr<number | null, any> {\n return new FnExpr(\"sum\", [this], {\n decode: decodeNumber,\n encode: identity,\n outputType: numericAggregateOutputType(\"sum\", this.outputType),\n }) as any;\n }\n\n avg(): Expr<number | null, any> {\n return new FnExpr(\"avg\", [this], {\n decode: decodeNumber,\n encode: identity,\n outputType: numericAggregateOutputType(\"avg\", this.outputType),\n }) as any;\n }\n\n min(): Expr<any, any> {\n return new FnExpr(\"min\", [this], {\n decode: (v) => this.decode(v),\n outputType: minMaxOutputType(this.outputType),\n }) as any;\n }\n\n max(): Expr<any, any> {\n return new FnExpr(\"max\", [this], {\n decode: (v) => this.decode(v),\n outputType: minMaxOutputType(this.outputType),\n }) as any;\n }\n\n arrayAgg(): Expr<any, any> {\n // Values are arrays while the argument encodes/decodes *elements*, i.e. a `.coalesce([\"b:1\"])`\n // fallback must encode each tagged id, not hand the whole array to the id column's encoder\n return new FnExpr(\"array_agg\", [this], {\n decode: (v) => (Array.isArray(v) ? v.map((e) => this.decode(e)) : v),\n encode: (v) => (Array.isArray(v) ? v.map((e) => this.encode(e)) : v),\n outputType: arrayOutputType(this.outputType),\n }) as any;\n }\n\n stringAgg(delimiter: string): Expr<string | null, any> {\n return new FnExpr(\"string_agg\", [this, new BindingExpr(delimiter)], {\n outputType:\n this.outputType?.domain === String ? { dbType: \"text\", domain: String, arrayElementSafe: true } : undefined,\n }) as any;\n }\n\n coalesce(fallback: unknown): Expr<any, never> {\n return new FnExpr(\"coalesce\", [this, new BindingExpr(this.encode(fallback))], {\n decode: (v) => this.decode(v),\n outputType: this.outputType,\n }) as any;\n }\n\n /** `this op value`, where `value` may be `undefined` (pruned), `null`, another expression, or a literal. */\n protected compare(op: string, value: unknown): ExpressionCondition {\n if (value === undefined) return skipCondition;\n if (value === null) {\n const not = op === \"=\" ? \"\" : op === \"!=\" ? \"NOT \" : fail(`Cannot compare ${op} to null`);\n return deferredCondition((ctx) => {\n const left = this.toSql(ctx);\n return { ...left, sql: `${left.sql} IS ${not}NULL` };\n });\n }\n if (isExpr(value)) {\n return deferredCondition((ctx) => joinFragments([this.toSql(ctx), asNode(value).toSql(ctx)], ` ${op} `));\n }\n return deferredCondition((ctx) => {\n const left = this.toSql(ctx);\n return { sql: `${left.sql} ${op} ?`, bindings: [...left.bindings, this.encode(value)], refs: left.refs };\n });\n }\n\n /** `this IN (subquery)` or `this = ANY(?)` for a list; `NOT IN` / `!= ALL(?)` for `nin`. */\n protected inList(op: \"IN\" | \"NOT IN\", values: unknown): ExpressionCondition {\n if (values === undefined) return skipCondition;\n if (isExpr(values)) {\n return deferredCondition((ctx) => {\n const left = this.toSql(ctx);\n const right = asNode(values).toSqlBare(ctx);\n return joinFragments([left, { ...right, sql: `(${right.sql})` }], ` ${op} `);\n });\n }\n if (!Array.isArray(values)) fail(`Expected an array or subquery for ${op}, got ${values}`);\n const fn = op === \"IN\" ? \"= ANY(?)\" : \"!= ALL(?)\";\n return deferredCondition((ctx) => {\n const left = this.toSql(ctx);\n return {\n sql: `${left.sql} ${fn}`,\n bindings: [...left.bindings, values.map((v) => this.encode(v))],\n refs: left.refs,\n };\n });\n }\n}\n\n/**\n * A SQL function applied to expressions, i.e. `count(a.\"id\")::int` or `coalesce(bs.\"n\", ?)`.\n *\n * By default decoding is identity and encoding follows the first argument. Callers explicitly supply\n * a decoder and output type when needed (`max(a.id)` is still an id); numeric aggregates supply their\n * own decoder/encoder, since `count(a.id)` is a number, not an id. Unknown functions have no output type.\n */\nexport class FnExpr extends BaseExpr {\n constructor(\n private name: string,\n private args: BaseExpr[],\n private opts: {\n prefix?: string;\n suffix?: string;\n decode?: (value: unknown) => unknown;\n encode?: (value: unknown) => unknown;\n outputType?: ExprOutputType;\n },\n ) {\n super();\n }\n\n get outputType(): ExprOutputType | undefined {\n return this.opts.outputType;\n }\n\n get sqlNullable(): boolean | undefined {\n switch (this.name) {\n case \"count\":\n return false;\n case \"sum\":\n case \"avg\":\n case \"min\":\n case \"max\":\n case \"array_agg\":\n case \"string_agg\":\n return true;\n case \"coalesce\":\n // Only the fallback is independent of an outer query's LEFT joins.\n return this.args[1]?.sqlNullable === false ? false : undefined;\n default:\n return undefined;\n }\n }\n\n toSql(ctx: ExprContext): SqlFragment {\n const args = joinFragments(\n this.args.map((a) => a.toSql(ctx)),\n \", \",\n );\n return { ...args, sql: `${this.name}(${this.opts.prefix ?? \"\"}${args.sql})${this.opts.suffix ?? \"\"}` };\n }\n\n decode(value: unknown): unknown {\n if (value === null || value === undefined) return value;\n return this.opts.decode ? this.opts.decode(value) : value;\n }\n\n encode(value: unknown): unknown {\n return this.opts.encode ? this.opts.encode(value) : this.args[0].encode(value);\n }\n}\n\n/** A bound literal, i.e. the `?` in `coalesce(x, ?)`. */\nexport class BindingExpr extends BaseExpr {\n constructor(private value: unknown) {\n super();\n }\n\n get sqlNullable(): boolean {\n return this.value === null || this.value === undefined;\n }\n\n toSql(): SqlFragment {\n return { sql: \"?\", bindings: [this.value], refs: [] };\n }\n}\n\n/** An unmodeled column on a known source, i.e. `sql.ref(a, \"ts_search\")`; untracked at the type level. */\nexport class RefExpr extends BaseExpr {\n constructor(\n private handle: object,\n private column: string,\n ) {\n super();\n }\n\n get sqlSource(): object {\n return this.handle;\n }\n\n toSql(ctx: ExprContext): SqlFragment {\n const alias = ctx.aliasFor(this.handle);\n // safeKq for both halves: sql.ref takes user strings, and a subquery alias is its `as` name\n return { sql: `${safeKq(alias)}.${safeKq(this.column)}`, bindings: [], refs: [alias] };\n }\n}\n\n/**\n * A `sql` tagged template.\n *\n * For an Author alias `a` assigned the SQL alias `a1`:\n *\n * ```ts\n * sql`${a.age} * 2` // Expression: a1.age * 2\n * sql`${a.age.gte(18)}` // Condition: (a1.age >= ?), bindings [18]\n * sql`${\"Alice\"}` // Value: ?, bindings [\"Alice\"]\n * ```\n */\nexport class TemplateExpr extends BaseExpr {\n constructor(\n private strings: TemplateStringsArray,\n private values: unknown[],\n ) {\n super();\n }\n\n toSql(ctx: ExprContext): SqlFragment {\n const parts: SqlFragment[] = [];\n this.strings.forEach((s, i) => {\n parts.push({ sql: s, bindings: [], refs: [] });\n if (i < this.values.length) parts.push(interpolationToSql(this.values[i], ctx));\n });\n return joinFragments(parts, \"\");\n }\n}\n\n/** Turns one `${...}` of a `sql` template into SQL: an expression, a condition, or a bound value. */\nexport function interpolationToSql(value: unknown, ctx: ExprContext): SqlFragment {\n if (isExpr(value)) {\n return asNode(value).toSql(ctx);\n } else if (isConditionLike(value)) {\n return ctx.conditionToSql(value as ExpressionCondition) ?? { sql: \"true\", bindings: [], refs: [] };\n } else {\n return { sql: \"?\", bindings: [value], refs: [] };\n }\n}\n\n/** True for the user-facing condition shapes: `{ and }`, `{ or }`, `ColumnCondition`, `RawCondition`. */\nexport function isConditionLike(value: unknown): boolean {\n if (typeof value !== \"object\" || value === null) return false;\n const v = value as any;\n return \"and\" in v || \"or\" in v || v.kind === \"column\" || v.kind === \"raw\" || v.kind === \"exists\";\n}\n\n/**\n * Normalizes known PostgreSQL type synonyms without treating coercible types or user types as equal.\n * The output compatibility checks compare these names, so normalize only spellings of the same type.\n *\n * I.e. `integer` and `int` both mean `int4`; `integer[]` and `int4[]` also describe the same SQL type.\n * Keeping these spellings distinct would reject compatible outputs just because their serdes use\n * different names for the same representation.\n *\n * I.e. `int4` can be promoted to `int8`, but they are not synonyms: classic pg returns int4 as a number\n * and int8 as a string. Treating them as equal could admit a compound whose first operand's decoder\n * cannot handle the common SQL type. This function does not choose or apply a conversion.\n *\n * I.e. `citext` must stay distinct from `text`: they disagree on whether 'Alice' and 'alice' are equal,\n * which matters when a set operation compares rows. A user-defined `app.email` domain over text can\n * also impose constraints that text does not. Unknown names remain unchanged, including their schema\n * and quoting; neither coercibility nor a shared base type proves that their outputs are compatible.\n */\nexport function canonicalDbType(dbType: string): string {\n if (dbType.endsWith(\"[]\")) return `${canonicalDbType(dbType.slice(0, -2))}[]`;\n switch (dbType) {\n case \"smallint\":\n return \"int2\";\n case \"int\":\n case \"integer\":\n return \"int4\";\n case \"bigint\":\n return \"int8\";\n case \"decimal\":\n return \"numeric\";\n case \"real\":\n return \"float4\";\n case \"double precision\":\n return \"float8\";\n case \"boolean\":\n return \"bool\";\n case \"character varying\":\n return \"varchar\";\n case \"character\":\n return \"bpchar\";\n case \"timestamp without time zone\":\n return \"timestamp\";\n case \"timestamp with time zone\":\n return \"timestamptz\";\n case \"time without time zone\":\n return \"time\";\n case \"time with time zone\":\n return \"timetz\";\n default:\n return dbType;\n }\n}\n\nconst arrayDomains = new Map<unknown, Map<EntityMetadata | undefined, symbol>>();\n\n// Both classic pg and lazy binary reads decode these physical arrays, not an unparsed array string.\nconst arrayElementDbTypes = new Set([\n \"bool\",\n \"bytea\",\n \"int2\",\n \"int4\",\n \"int8\",\n \"float4\",\n \"float8\",\n \"numeric\",\n \"text\",\n \"varchar\",\n \"bpchar\",\n \"uuid\",\n \"json\",\n \"jsonb\",\n]);\n\n/**\n * Describes elementwise conversion only for known physical array parsers and stable domain conversion.\n *\n * I.e. `a.id.arrayAgg()` wraps Author's tag and `idMeta` in its domain token, but has no scalar\n * `idMeta` of its own. Repeated expressions share the token; IDs of subtypes with a shared tag do not.\n * Native enum/citext, date/time, and nested SQL arrays remain unknown. Numeric elements can be numbers\n * in classic pg and strings in lazy binary reads, so a scalar string-only mapper is not sufficient.\n */\nexport function arrayOutputType(outputType: ExprOutputType | undefined): ExprOutputType | undefined {\n if (!outputType?.arrayElementSafe || !arrayElementDbTypes.has(outputType.dbType)) return undefined;\n let domains = arrayDomains.get(outputType.domain);\n if (!domains) arrayDomains.set(outputType.domain, (domains = new Map()));\n let domain = domains.get(outputType.idMeta);\n if (!domain) domains.set(outputType.idMeta, (domain = Symbol(\"joist.arrayOutput\")));\n return { dbType: `${outputType.dbType}[]`, domain };\n}\n\n/**\n * Resolves the SQL result type of supported numeric aggregates; other overloads remain unknown.\n *\n * I.e. `a.age.sum()` has `dbType: \"int8\"` and Number conversion, while `a.age` has `dbType: \"int4\"`\n * and identity conversion. Their number domains agree, but SQL types reject a union in either order.\n */\nfunction numericAggregateOutputType(\n name: \"sum\" | \"avg\",\n outputType: ExprOutputType | undefined,\n): ExprOutputType | undefined {\n switch (outputType?.dbType) {\n case \"int2\":\n case \"int4\":\n return { dbType: name === \"sum\" ? \"int8\" : \"numeric\", domain: Number, arrayElementSafe: true };\n case \"int8\":\n case \"numeric\":\n return { dbType: \"numeric\", domain: Number, arrayElementSafe: true };\n case \"float4\":\n return { dbType: name === \"sum\" ? \"float4\" : \"float8\", domain: Number, arrayElementSafe: true };\n case \"float8\":\n return { dbType: \"float8\", domain: Number, arrayElementSafe: true };\n default:\n return undefined;\n }\n}\n\n/** Only known MIN/MAX overloads have predictable output types; varchar/name use the text overload. */\nfunction minMaxOutputType(outputType: ExprOutputType | undefined): ExprOutputType | undefined {\n switch (outputType?.dbType) {\n case \"varchar\":\n case \"name\":\n return { ...outputType, dbType: \"text\" };\n case \"int2\":\n case \"int4\":\n case \"int8\":\n case \"numeric\":\n case \"float4\":\n case \"float8\":\n case \"text\":\n case \"bpchar\":\n case \"date\":\n case \"time\":\n case \"timetz\":\n case \"timestamp\":\n case \"timestamptz\":\n case \"interval\":\n case \"money\":\n case \"inet\":\n return outputType;\n default:\n return undefined;\n }\n}\n\n/** An `AliasResolver` backed by an `ExprContext`; a handle's bound meta is its own (`em.query` sources are their own tables). */\nfunction ctxResolver(ctx: ExprContext): AliasResolver {\n return function resolve(handle) {\n return { meta: handle.meta, alias: ctx.aliasFor(handle) };\n };\n}\n\n/** Decodes `count`/`sum`/`avg` results, which Postgres returns as strings for bigint/numeric. */\nfunction decodeNumber(value: unknown): unknown {\n return typeof value === \"string\" ? Number(value) : value;\n}\n\nfunction identity(value: unknown): unknown {\n return value;\n}\n\nfunction fail(message: string): never {\n throw new Error(message);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Expr.cjs","names":["skipCondition","safeKq"],"sources":["../src/Expr.ts"],"sourcesContent":["import type { ExpressionCondition } from \"./EntityFilter.ts\";\nimport type { EntityMetadata } from \"./EntityMetadata.ts\";\nimport { safeKq } from \"./keywords.ts\";\nimport type { RawCondition } from \"./QueryParser.ts\";\nimport { skipCondition } from \"./skipCondition.ts\";\n\n/**\n * The shared expression protocol for `em.query`.\n *\n * Table columns (`a.first_name`), aggregates (`b.id.count()`), `sql` templates, subquery columns\n * (`bookStats.bookCount`), and scalar subqueries all implement it, so any of them can appear in\n * `select`, `where`, `groupBy`, `having`, `orderBy`, and inside other expressions.\n *\n * This module is a leaf on purpose: `Tables.ts` extends `BaseExpr` at load time, so nothing here may\n * import (at runtime) a module that leads back to `Tables.ts`. Anything that needs metadata, alias\n * binding, or SQL generation for conditions is reached through the `ExprContext` the query parser passes in.\n */\n\nexport const exprBrand: unique symbol = Symbol(\"joist.expr\");\n\n/**\n * Phantom type information carried by every `Expr`.\n *\n * `R` is the decoded result type.\n *\n * `Src` is the expression's *source key*: the type-level identity of the table it reads from. An entity\n * table's key is its type name (`table(Author)` gives `\"Author\"`) or the explicit name in\n * `table(Author, \"m\")`; a subquery's key is its `as: \"book_stats\"`, or the shared sentinel `\"?\"` when it\n * has no `as`. Exactly two questions are asked of a source key, and nothing else:\n *\n * - `MaybeNull` asks \"is my source key among the LEFT-joined sources in this query's join list?\" If yes,\n * the value can be `null`.\n * - `CheckScope` asks \"is my source key among `from` + `join` at all?\" If no, the query reads from a\n * table it never joined.\n *\n * Two special keys opt out of both questions:\n *\n * - `Expr<number, never>` reads from nothing that can be left-joined away, i.e. `b.id.count()`\n * - `Expr<number, string>` (the default) is untracked, i.e. `sql.ref` on an unknown table\n */\nexport interface ExprBrand<R, Src extends string> {\n readonly __result: R;\n readonly __source: Src;\n}\n\n/**\n * Runtime type information that lets set operations reject outputs that cannot safely share conversions.\n *\n * PostgreSQL compares projected SQL rows and chooses a common SQL type before Joist decodes them.\n * A compound uses one decoder per output column and retains an encoder for later comparisons and\n * coalesce fallbacks. We must check every operand before reusing the first operand's conversions:\n * TypeScript result types are erased, and equal SQL storage types do not imply equal logical domains.\n *\n * I.e. Author.id and Book.author_id are compatible Author IDs despite having separate serdes, while\n * Book.id must not compare or decode as an Author ID just because both use int4. Conversely, Author.age\n * and Author.age.sum() both expose TypeScript numbers but produce int4 and int8 driver values. Without\n * their SQL type information, accepting them could make decoding depend on which operand comes first.\n * Enum, custom-mapper, and JSON-schema domain tokens also prevent unrelated conversions from mixing.\n *\n * Compatibility requires exact equality of the canonical SQL `dbType`, the `domain` token, and\n * `idMeta`. An absent descriptor is unknown, not an identity codec. `idMeta` identifies scalar IDs only;\n * arrays retain their element domain separately so they cannot select a polymorphic IN component.\n * Raw `sql<R>` annotations do not supply this runtime information. Encoding and decoding remain on the\n * expression; this descriptor only holds the information needed for these conservative checks.\n */\nexport interface ExprOutputType {\n dbType: string;\n domain: unknown;\n idMeta?: EntityMetadata;\n /** Internal conversion proof, consistent for equal dbType/domain/idMeta; not another compatibility key. */\n arrayElementSafe?: boolean;\n}\n\n/**\n * \"Any expression whose result is `R`\", checked by brand alone.\n *\n * Method parameters use this instead of `Expr<R>` so that `Expr` stays covariant in `R`: checking\n * `Expr<AuthorId>` against `Expr<AuthorId | null>` then only compares the phantom `__result`, not every\n * method's parameter list (which would make `Expr` invariant, and break `select: b.author_id` dispatch and\n * polymorphic joins).\n */\nexport type ExprLike<R> = { readonly [exprBrand]: ExprBrand<R, any> };\n\n/**\n * A typed SQL expression: a table column, an aggregate, a `sql` template, or a scalar subquery.\n *\n * Conditions and SQL functions are methods, so they need no import. Aggregates keep `Src` so scope\n * checking still sees through `bs.x.max()`; `count` is source-less because `count(x)` is 0, not null,\n * when `x`'s table is left-joined away; `coalesce` drops `Src` on purpose, since its whole job is to\n * remove the nullability a left join adds.\n */\nexport interface Expr<R, Src extends string = string> {\n readonly [exprBrand]: ExprBrand<R, Src>;\n eq(value: R | ExprLike<R> | undefined): ExpressionCondition;\n ne(value: R | ExprLike<R> | undefined): ExpressionCondition;\n gt(value: R | ExprLike<R> | undefined): ExpressionCondition;\n gte(value: R | ExprLike<R> | undefined): ExpressionCondition;\n lt(value: R | ExprLike<R> | undefined): ExpressionCondition;\n lte(value: R | ExprLike<R> | undefined): ExpressionCondition;\n // A list subquery may select a nullable column; NULLs in the set never match, so that is fine.\n in(values: readonly R[] | ExprLike<R | null> | undefined): ExpressionCondition;\n nin(values: readonly R[] | ExprLike<R | null> | undefined): ExpressionCondition;\n\n /** `count(x)::int`; `count(a.id)` is `count(*)` for the FROM table, and the matched-row count for a left-joined one. */\n count(): Expr<number, never>;\n countDistinct(): Expr<number, never>;\n sum(this: Expr<number | null, Src>): Expr<number | null, Src>;\n avg(this: Expr<number | null, Src>): Expr<number | null, Src>;\n min(): Expr<R | null, Src>;\n max(): Expr<R | null, Src>;\n /** PG keeps element NULLs (a left-joined empty group aggregates as `[null]`), and zero rows aggregate as NULL. */\n arrayAgg(): Expr<R[] | null, Src>;\n stringAgg(this: Expr<string | null, Src>, delimiter: string): Expr<string | null, Src>;\n coalesce(fallback: NonNullable<R>): Expr<NonNullable<R>, never>;\n}\n\n/**\n * A join entry: the join kind is the key, the joined source is the value, plus `on`. `inner?: never` /\n * `left?: never` keep an entry to one kind (the `ExpressionFilter` `and`/`or` trick).\n *\n * `on` is required. A join is pruned when nothing references it anymore, not by an `undefined` ON;\n * `keep: true` pins a join that would otherwise prune, i.e. an inner join used as an existence filter,\n * the way em.find's `keepAliases` does. It is a boolean so callers can pass a flag.\n *\n * Declared here (not `query.ts`) so the relation join factories in `Tables.ts` (i.e. `a.books.as(b)`) can\n * return them without importing `query.ts`; `query.ts` re-constrains `A` to its `QuerySource`.\n */\nexport interface InnerJoin<A> {\n readonly inner: A;\n readonly left?: never;\n readonly on: ExpressionCondition;\n readonly keep?: boolean;\n}\n\nexport interface LeftJoin<A> {\n readonly left: A;\n readonly inner?: never;\n readonly on: ExpressionCondition;\n readonly keep?: boolean;\n}\n\n/** SQL plus its `?` bindings plus the SQL aliases it references, i.e. for join pruning. */\nexport interface SqlFragment {\n sql: string;\n bindings: any[];\n refs: string[];\n}\n\n/**\n * What an expression needs from the query it is generating SQL for: the SQL alias assigned to each\n * source, and a way to turn nested conditions into SQL (which needs `ConditionBuilder`, so it lives in `query.ts`).\n */\nexport interface ExprContext {\n /** Returns the SQL alias for a table's `TableMgmt` or a subquery handle, searching enclosing queries. */\n aliasFor(handle: object): string;\n /** Turns a user-facing condition into SQL; `undefined` if it pruned away entirely. */\n conditionToSql(cond: ExpressionCondition): SqlFragment | undefined;\n}\n\nexport function isExpr(value: unknown): value is ExprLike<any> {\n return typeof value === \"object\" && value !== null && exprBrand in value;\n}\n\n/** Every `Expr` is a `BaseExpr` at runtime; this cast keeps `isExpr` a plain type guard so unions narrow. */\nexport function asNode(expr: ExprLike<any>): BaseExpr {\n return expr as any as BaseExpr;\n}\n\nexport const deferredSym: unique symbol = Symbol(\"joist.deferredCondition\");\n\n/**\n * A condition whose SQL depends on aliases that are only known once the query is parsed, i.e.\n * `bookStats.bookCount.gt(1)` or `bs.authorId.eq(a.id)`.\n *\n * It is shaped like a `RawCondition` so it can sit in any `ExpressionFilter`; `resolveDeferredConditions`\n * snapshots `condition`, `bindings`, and `aliases` before the filter is parsed. Domain alias conditions\n * use a separate protocol in the `em.find` parser.\n */\nexport interface DeferredCondition extends RawCondition {\n [deferredSym]: (ctx: ExprContext) => RawCondition;\n}\n\nexport function isDeferredCondition(cond: unknown): cond is DeferredCondition {\n return typeof cond === \"object\" && cond !== null && deferredSym in cond;\n}\n\n/** Creates a `DeferredCondition` that generates its SQL with `fn` once the query's aliases are known. */\nexport function deferredCondition(fn: (ctx: ExprContext) => SqlFragment): DeferredCondition {\n const cond: DeferredCondition = {\n kind: \"raw\",\n aliases: [],\n condition: \"<unresolved>\",\n bindings: [],\n pruneable: false,\n [deferredSym]: (ctx) => {\n const { sql, bindings, refs } = fn(ctx);\n return { ...cond, condition: sql, bindings, aliases: refs };\n },\n };\n return cond;\n}\n\n/**\n * Resolve each condition occurrence and snapshot it before resolving the next one. A nested subquery\n * may reuse the same condition under another alias; it must not overwrite this occurrence's SQL.\n */\nexport function resolveDeferredConditions(\n cond: ExpressionCondition | undefined,\n ctx: ExprContext,\n): ExpressionCondition | undefined {\n if (cond === undefined || cond === null) return cond;\n if (isDeferredCondition(cond)) {\n return cond[deferredSym](ctx);\n } else if (\"and\" in cond && cond.and) {\n return { ...cond, and: cond.and.map((c) => resolveDeferredConditions(c, ctx)) };\n } else if (\"or\" in cond && cond.or) {\n return { ...cond, or: cond.or.map((c) => resolveDeferredConditions(c, ctx)) };\n }\n return cond;\n}\n\n/** Concatenates SQL fragments with `sep`, keeping bindings and refs in order. */\nexport function joinFragments(parts: SqlFragment[], sep: string): SqlFragment {\n return {\n sql: parts.map((p) => p.sql).join(sep),\n bindings: parts.flatMap((p) => p.bindings),\n refs: parts.flatMap((p) => p.refs),\n };\n}\n\n/**\n * The methods every expression shares. Subclasses provide `toSql`, and usually `decode`/`encode`.\n *\n * Table columns can override comparisons to apply column-specific conversions. Domain aliases do not\n * implement this protocol.\n */\nexport abstract class BaseExpr {\n readonly [exprBrand]: any = this;\n\n /** Produces this expression's SQL so it can be embedded in a larger expression, i.e. a subquery gets parens. */\n abstract toSql(ctx: ExprContext): SqlFragment;\n\n /** Only an actual scalar/IN subquery exposes its selected expression, not an ordinary ID expression. */\n get subquerySelect(): BaseExpr | undefined {\n return undefined;\n }\n\n /** Known SQL representation and logical domain; raw SQL and unmodeled refs remain unknown. */\n get outputType(): ExprOutputType | undefined {\n return undefined;\n }\n\n /** Physical SQL nullability; undefined means unknown, not a NOT NULL guarantee. */\n get sqlNullable(): boolean | undefined {\n return undefined;\n }\n\n /** The source of a direct column reference, whose value becomes NULL under an unmatched LEFT join. */\n get sqlSource(): object | undefined {\n return undefined;\n }\n\n /** Produces the SQL without the outer parens a subquery normally gets; only differs for subqueries. */\n toSqlBare(ctx: ExprContext): SqlFragment {\n return this.toSql(ctx);\n }\n\n /** Converts a result-set value into the domain value, i.e. an int into a tagged id. */\n decode(value: unknown): unknown {\n return value;\n }\n\n /** Converts a domain value into the database value, i.e. a tagged id into an int, for bindings. */\n encode(value: unknown): unknown {\n return value;\n }\n\n eq(value: unknown): ExpressionCondition {\n return this.compare(\"=\", value);\n }\n\n ne(value: unknown): ExpressionCondition {\n return this.compare(\"!=\", value);\n }\n\n gt(value: unknown): ExpressionCondition {\n return this.compare(\">\", value);\n }\n\n gte(value: unknown): ExpressionCondition {\n return this.compare(\">=\", value);\n }\n\n lt(value: unknown): ExpressionCondition {\n return this.compare(\"<\", value);\n }\n\n lte(value: unknown): ExpressionCondition {\n return this.compare(\"<=\", value);\n }\n\n in(values: unknown): ExpressionCondition {\n return this.inList(\"IN\", values);\n }\n\n nin(values: unknown): ExpressionCondition {\n return this.inList(\"NOT IN\", values);\n }\n\n count(): Expr<number, never> {\n return new FnExpr(\"count\", [this], {\n suffix: \"::int\",\n decode: decodeNumber,\n encode: identity,\n outputType: { dbType: \"int4\", domain: Number, arrayElementSafe: true },\n }) as any;\n }\n\n countDistinct(): Expr<number, never> {\n return new FnExpr(\"count\", [this], {\n prefix: \"DISTINCT \",\n suffix: \"::int\",\n decode: decodeNumber,\n encode: identity,\n outputType: { dbType: \"int4\", domain: Number, arrayElementSafe: true },\n }) as any;\n }\n\n sum(): Expr<number | null, any> {\n return new FnExpr(\"sum\", [this], {\n decode: decodeNumber,\n encode: identity,\n outputType: numericAggregateOutputType(\"sum\", this.outputType),\n }) as any;\n }\n\n avg(): Expr<number | null, any> {\n return new FnExpr(\"avg\", [this], {\n decode: decodeNumber,\n encode: identity,\n outputType: numericAggregateOutputType(\"avg\", this.outputType),\n }) as any;\n }\n\n min(): Expr<any, any> {\n return new FnExpr(\"min\", [this], {\n decode: (v) => this.decode(v),\n outputType: minMaxOutputType(this.outputType),\n }) as any;\n }\n\n max(): Expr<any, any> {\n return new FnExpr(\"max\", [this], {\n decode: (v) => this.decode(v),\n outputType: minMaxOutputType(this.outputType),\n }) as any;\n }\n\n arrayAgg(): Expr<any, any> {\n // Values are arrays while the argument encodes/decodes *elements*, i.e. a `.coalesce([\"b:1\"])`\n // fallback must encode each tagged id, not hand the whole array to the id column's encoder\n return new FnExpr(\"array_agg\", [this], {\n decode: (v) => (Array.isArray(v) ? v.map((e) => this.decode(e)) : v),\n encode: (v) => (Array.isArray(v) ? v.map((e) => this.encode(e)) : v),\n outputType: arrayOutputType(this.outputType),\n }) as any;\n }\n\n stringAgg(delimiter: string): Expr<string | null, any> {\n return new FnExpr(\"string_agg\", [this, new BindingExpr(delimiter)], {\n outputType:\n this.outputType?.domain === String ? { dbType: \"text\", domain: String, arrayElementSafe: true } : undefined,\n }) as any;\n }\n\n coalesce(fallback: unknown): Expr<any, never> {\n return new FnExpr(\"coalesce\", [this, new BindingExpr(this.encode(fallback))], {\n decode: (v) => this.decode(v),\n outputType: this.outputType,\n }) as any;\n }\n\n /** `this op value`, where `value` may be `undefined` (pruned), `null`, another expression, or a literal. */\n protected compare(op: string, value: unknown): ExpressionCondition {\n if (value === undefined) return skipCondition;\n if (value === null) {\n const not = op === \"=\" ? \"\" : op === \"!=\" ? \"NOT \" : fail(`Cannot compare ${op} to null`);\n return deferredCondition((ctx) => {\n const left = this.toSql(ctx);\n return { ...left, sql: `${left.sql} IS ${not}NULL` };\n });\n }\n if (isExpr(value)) {\n return deferredCondition((ctx) => joinFragments([this.toSql(ctx), asNode(value).toSql(ctx)], ` ${op} `));\n }\n return deferredCondition((ctx) => {\n const left = this.toSql(ctx);\n return { sql: `${left.sql} ${op} ?`, bindings: [...left.bindings, this.encode(value)], refs: left.refs };\n });\n }\n\n /** `this IN (subquery)` or `this = ANY(?)` for a list; `NOT IN` / `!= ALL(?)` for `nin`. */\n protected inList(op: \"IN\" | \"NOT IN\", values: unknown): ExpressionCondition {\n if (values === undefined) return skipCondition;\n if (isExpr(values)) {\n return deferredCondition((ctx) => {\n const left = this.toSql(ctx);\n const right = asNode(values).toSqlBare(ctx);\n return joinFragments([left, { ...right, sql: `(${right.sql})` }], ` ${op} `);\n });\n }\n if (!Array.isArray(values)) fail(`Expected an array or subquery for ${op}, got ${values}`);\n const fn = op === \"IN\" ? \"= ANY(?)\" : \"!= ALL(?)\";\n return deferredCondition((ctx) => {\n const left = this.toSql(ctx);\n return {\n sql: `${left.sql} ${fn}`,\n bindings: [...left.bindings, values.map((v) => this.encode(v))],\n refs: left.refs,\n };\n });\n }\n}\n\n/**\n * A SQL function applied to expressions, i.e. `count(a.\"id\")::int` or `coalesce(bs.\"n\", ?)`.\n *\n * By default decoding is identity and encoding follows the first argument. Callers explicitly supply\n * a decoder and output type when needed (`max(a.id)` is still an id); numeric aggregates supply their\n * own decoder/encoder, since `count(a.id)` is a number, not an id. Unknown functions have no output type.\n */\nexport class FnExpr extends BaseExpr {\n constructor(\n private name: string,\n private args: BaseExpr[],\n private opts: {\n prefix?: string;\n suffix?: string;\n decode?: (value: unknown) => unknown;\n encode?: (value: unknown) => unknown;\n outputType?: ExprOutputType;\n },\n ) {\n super();\n }\n\n get outputType(): ExprOutputType | undefined {\n return this.opts.outputType;\n }\n\n get sqlNullable(): boolean | undefined {\n switch (this.name) {\n case \"count\":\n return false;\n case \"sum\":\n case \"avg\":\n case \"min\":\n case \"max\":\n case \"array_agg\":\n case \"string_agg\":\n return true;\n case \"coalesce\":\n // Only the fallback is independent of an outer query's LEFT joins.\n return this.args[1]?.sqlNullable === false ? false : undefined;\n default:\n return undefined;\n }\n }\n\n toSql(ctx: ExprContext): SqlFragment {\n const args = joinFragments(\n this.args.map((a) => a.toSql(ctx)),\n \", \",\n );\n return { ...args, sql: `${this.name}(${this.opts.prefix ?? \"\"}${args.sql})${this.opts.suffix ?? \"\"}` };\n }\n\n decode(value: unknown): unknown {\n if (value === null || value === undefined) return value;\n return this.opts.decode ? this.opts.decode(value) : value;\n }\n\n encode(value: unknown): unknown {\n return this.opts.encode ? this.opts.encode(value) : this.args[0].encode(value);\n }\n}\n\n/** A bound literal, i.e. the `?` in `coalesce(x, ?)`. */\nexport class BindingExpr extends BaseExpr {\n constructor(private value: unknown) {\n super();\n }\n\n get sqlNullable(): boolean {\n return this.value === null || this.value === undefined;\n }\n\n toSql(): SqlFragment {\n return { sql: \"?\", bindings: [this.value], refs: [] };\n }\n}\n\n/** An unmodeled column on a known source, i.e. `sql.ref(a, \"ts_search\")`; untracked at the type level. */\nexport class RefExpr extends BaseExpr {\n constructor(\n private handle: object,\n private column: string,\n ) {\n super();\n }\n\n get sqlSource(): object {\n return this.handle;\n }\n\n toSql(ctx: ExprContext): SqlFragment {\n const alias = ctx.aliasFor(this.handle);\n // safeKq for both halves: sql.ref takes user strings, and a subquery alias is its `as` name\n return { sql: `${safeKq(alias)}.${safeKq(this.column)}`, bindings: [], refs: [alias] };\n }\n}\n\n/**\n * A `sql` tagged template.\n *\n * For an Author table `a` assigned the SQL alias `a1`:\n *\n * ```ts\n * sql`${a.age} * 2` // Expression: a1.age * 2\n * sql`${a.age.gte(18)}` // Condition: (a1.age >= ?), bindings [18]\n * sql`${\"Alice\"}` // Value: ?, bindings [\"Alice\"]\n * ```\n */\nexport class TemplateExpr extends BaseExpr {\n constructor(\n private strings: TemplateStringsArray,\n private values: unknown[],\n ) {\n super();\n }\n\n toSql(ctx: ExprContext): SqlFragment {\n const parts: SqlFragment[] = [];\n this.strings.forEach((s, i) => {\n parts.push({ sql: s, bindings: [], refs: [] });\n if (i < this.values.length) parts.push(interpolationToSql(this.values[i], ctx));\n });\n return joinFragments(parts, \"\");\n }\n}\n\n/** Turns one `${...}` of a `sql` template into SQL: an expression, a condition, or a bound value. */\nexport function interpolationToSql(value: unknown, ctx: ExprContext): SqlFragment {\n if (isExpr(value)) {\n return asNode(value).toSql(ctx);\n } else if (isConditionLike(value)) {\n return ctx.conditionToSql(value as ExpressionCondition) ?? { sql: \"true\", bindings: [], refs: [] };\n } else {\n return { sql: \"?\", bindings: [value], refs: [] };\n }\n}\n\n/** True for the user-facing condition shapes: `{ and }`, `{ or }`, `ColumnCondition`, `RawCondition`. */\nexport function isConditionLike(value: unknown): boolean {\n if (typeof value !== \"object\" || value === null) return false;\n const v = value as any;\n return \"and\" in v || \"or\" in v || v.kind === \"column\" || v.kind === \"raw\" || v.kind === \"exists\";\n}\n\n/**\n * Normalizes known PostgreSQL type synonyms without treating coercible types or user types as equal.\n * The output compatibility checks compare these names, so normalize only spellings of the same type.\n *\n * I.e. `integer` and `int` both mean `int4`; `integer[]` and `int4[]` also describe the same SQL type.\n * Keeping these spellings distinct would reject compatible outputs just because their serdes use\n * different names for the same representation.\n *\n * I.e. `int4` can be promoted to `int8`, but they are not synonyms: classic pg returns int4 as a number\n * and int8 as a string. Treating them as equal could admit a compound whose first operand's decoder\n * cannot handle the common SQL type. This function does not choose or apply a conversion.\n *\n * I.e. `citext` must stay distinct from `text`: they disagree on whether 'Alice' and 'alice' are equal,\n * which matters when a set operation compares rows. A user-defined `app.email` domain over text can\n * also impose constraints that text does not. Unknown names remain unchanged, including their schema\n * and quoting; neither coercibility nor a shared base type proves that their outputs are compatible.\n */\nexport function canonicalDbType(dbType: string): string {\n if (dbType.endsWith(\"[]\")) return `${canonicalDbType(dbType.slice(0, -2))}[]`;\n switch (dbType) {\n case \"smallint\":\n return \"int2\";\n case \"int\":\n case \"integer\":\n return \"int4\";\n case \"bigint\":\n return \"int8\";\n case \"decimal\":\n return \"numeric\";\n case \"real\":\n return \"float4\";\n case \"double precision\":\n return \"float8\";\n case \"boolean\":\n return \"bool\";\n case \"character varying\":\n return \"varchar\";\n case \"character\":\n return \"bpchar\";\n case \"timestamp without time zone\":\n return \"timestamp\";\n case \"timestamp with time zone\":\n return \"timestamptz\";\n case \"time without time zone\":\n return \"time\";\n case \"time with time zone\":\n return \"timetz\";\n default:\n return dbType;\n }\n}\n\nconst arrayDomains = new Map<unknown, Map<EntityMetadata | undefined, symbol>>();\n\n// Both classic pg and lazy binary reads decode these physical arrays, not an unparsed array string.\nconst arrayElementDbTypes = new Set([\n \"bool\",\n \"bytea\",\n \"int2\",\n \"int4\",\n \"int8\",\n \"float4\",\n \"float8\",\n \"numeric\",\n \"text\",\n \"varchar\",\n \"bpchar\",\n \"uuid\",\n \"json\",\n \"jsonb\",\n]);\n\n/**\n * Describes elementwise conversion only for known physical array parsers and stable domain conversion.\n *\n * I.e. `a.id.arrayAgg()` wraps Author's tag and `idMeta` in its domain token, but has no scalar\n * `idMeta` of its own. Repeated expressions share the token; IDs of subtypes with a shared tag do not.\n * Native enum/citext, date/time, and nested SQL arrays remain unknown. Numeric elements can be numbers\n * in classic pg and strings in lazy binary reads, so a scalar string-only mapper is not sufficient.\n */\nexport function arrayOutputType(outputType: ExprOutputType | undefined): ExprOutputType | undefined {\n if (!outputType?.arrayElementSafe || !arrayElementDbTypes.has(outputType.dbType)) return undefined;\n let domains = arrayDomains.get(outputType.domain);\n if (!domains) arrayDomains.set(outputType.domain, (domains = new Map()));\n let domain = domains.get(outputType.idMeta);\n if (!domain) domains.set(outputType.idMeta, (domain = Symbol(\"joist.arrayOutput\")));\n return { dbType: `${outputType.dbType}[]`, domain };\n}\n\n/**\n * Resolves the SQL result type of supported numeric aggregates; other overloads remain unknown.\n *\n * I.e. `a.age.sum()` has `dbType: \"int8\"` and Number conversion, while `a.age` has `dbType: \"int4\"`\n * and identity conversion. Their number domains agree, but SQL types reject a union in either order.\n */\nfunction numericAggregateOutputType(\n name: \"sum\" | \"avg\",\n outputType: ExprOutputType | undefined,\n): ExprOutputType | undefined {\n switch (outputType?.dbType) {\n case \"int2\":\n case \"int4\":\n return { dbType: name === \"sum\" ? \"int8\" : \"numeric\", domain: Number, arrayElementSafe: true };\n case \"int8\":\n case \"numeric\":\n return { dbType: \"numeric\", domain: Number, arrayElementSafe: true };\n case \"float4\":\n return { dbType: name === \"sum\" ? \"float4\" : \"float8\", domain: Number, arrayElementSafe: true };\n case \"float8\":\n return { dbType: \"float8\", domain: Number, arrayElementSafe: true };\n default:\n return undefined;\n }\n}\n\n/** Only known MIN/MAX overloads have predictable output types; varchar/name use the text overload. */\nfunction minMaxOutputType(outputType: ExprOutputType | undefined): ExprOutputType | undefined {\n switch (outputType?.dbType) {\n case \"varchar\":\n case \"name\":\n return { ...outputType, dbType: \"text\" };\n case \"int2\":\n case \"int4\":\n case \"int8\":\n case \"numeric\":\n case \"float4\":\n case \"float8\":\n case \"text\":\n case \"bpchar\":\n case \"date\":\n case \"time\":\n case \"timetz\":\n case \"timestamp\":\n case \"timestamptz\":\n case \"interval\":\n case \"money\":\n case \"inet\":\n return outputType;\n default:\n return undefined;\n }\n}\n\n/** Decodes `count`/`sum`/`avg` results, which Postgres returns as strings for bigint/numeric. */\nfunction decodeNumber(value: unknown): unknown {\n return typeof value === \"string\" ? Number(value) : value;\n}\n\nfunction identity(value: unknown): unknown {\n return value;\n}\n\nfunction fail(message: string): never {\n throw new Error(message);\n}\n"],"mappings":"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"}
|