prisma-ltree 0.5.0 → 0.7.0

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.
Files changed (35) hide show
  1. package/README.md +41 -8
  2. package/dist/{codec-types-BJE-5cwk.d.mts → codec-types-B0DtEnU6.d.mts} +16 -13
  3. package/dist/codec-types-B0DtEnU6.d.mts.map +1 -0
  4. package/dist/codec-types.d.mts +1 -1
  5. package/dist/control.d.mts.map +1 -1
  6. package/dist/control.mjs +236 -16
  7. package/dist/control.mjs.map +1 -1
  8. package/dist/{descriptor-meta-2In8FI1I.mjs → descriptor-meta-DwbFei9y.mjs} +341 -34
  9. package/dist/descriptor-meta-DwbFei9y.mjs.map +1 -0
  10. package/dist/{operation-types-8su4lbK-.d.mts → operation-types-DD-KyBms.d.mts} +46 -1
  11. package/dist/{operation-types-8su4lbK-.d.mts.map → operation-types-DD-KyBms.d.mts.map} +1 -1
  12. package/dist/operation-types.d.mts +1 -1
  13. package/dist/pack.d.mts +1 -1
  14. package/dist/pack.d.mts.map +1 -1
  15. package/dist/pack.mjs +1 -1
  16. package/dist/runtime.d.mts +35 -1
  17. package/dist/runtime.d.mts.map +1 -1
  18. package/dist/runtime.mjs +34 -1
  19. package/dist/runtime.mjs.map +1 -1
  20. package/package.json +11 -10
  21. package/src/contract.d.ts +87 -20
  22. package/src/contract.json +22 -1
  23. package/src/contract.ts +21 -3
  24. package/src/core/codecs-validation.ts +31 -0
  25. package/src/core/codecs.ts +32 -55
  26. package/src/core/contract-space-constants.ts +11 -0
  27. package/src/core/data-types.ts +36 -2
  28. package/src/core/descriptor-meta.ts +64 -4
  29. package/src/core/hierarchy-collection-methods.ts +111 -0
  30. package/src/core/pattern-validation.ts +313 -0
  31. package/src/exports/control.ts +36 -1
  32. package/src/exports/runtime.ts +3 -0
  33. package/src/types/operation-types.ts +37 -0
  34. package/dist/codec-types-BJE-5cwk.d.mts.map +0 -1
  35. package/dist/descriptor-meta-2In8FI1I.mjs.map +0 -1
@@ -1,54 +1,24 @@
1
- import type { JsonValue } from "@prisma/orm-framework/contract/types";
2
1
  import {
2
+ type AnyCodecDescriptor,
3
3
  type CodecCallContext,
4
4
  CodecImpl,
5
5
  type CodecInstanceContext,
6
6
  type ColumnHelperFor,
7
7
  type ColumnHelperForStrict,
8
8
  column,
9
+ type DataTypeValue,
9
10
  } from "@prisma/orm-framework/components/codec";
10
11
  import type { ExtractCodecTypes, ProjectionExpr } from "@prisma/orm-family-sql/relational-core/ast";
12
+ import { CastExpr } from "@prisma/orm-family-sql/relational-core/ast";
11
13
  import {
12
14
  definePostgresCodecs,
13
15
  PostgresCodecDescriptor,
14
16
  } from "@prisma/orm-target-postgres/target/codec-descriptor";
15
- import {
16
- LTREE_ARRAY_CODEC_ID,
17
- LTREE_CODEC_ID,
18
- LTREE_MAX_LABEL_LENGTH,
19
- LTREE_MAX_LABELS,
20
- } from "./constants";
17
+ import { assertValidLtree } from "./codecs-validation";
18
+ import { LTREE_ARRAY_CODEC_ID, LTREE_CODEC_ID } from "./constants";
21
19
  import { ltreeArrayDataType, ltreeDataType } from "./data-types";
22
20
 
23
- const LABEL_PATTERN = /^[A-Za-z0-9_-]+$/;
24
-
25
- export function assertValidLtree(value: unknown): asserts value is string {
26
- if (typeof value !== "string") {
27
- throw new Error("ltree value must be a string");
28
- }
29
- if (value.length === 0) {
30
- throw new Error("ltree value must not be empty");
31
- }
32
- const labels = value.split(".");
33
- if (labels.length > LTREE_MAX_LABELS) {
34
- throw new Error(`ltree path exceeds max labels: got ${labels.length}, max ${LTREE_MAX_LABELS}`);
35
- }
36
- for (const label of labels) {
37
- if (label.length === 0) {
38
- throw new Error(`ltree label must not be empty in path "${value}"`);
39
- }
40
- if (label.length > LTREE_MAX_LABEL_LENGTH) {
41
- throw new Error(
42
- `ltree label exceeds max length: got ${label.length}, max ${LTREE_MAX_LABEL_LENGTH}`,
43
- );
44
- }
45
- if (!LABEL_PATTERN.test(label)) {
46
- throw new Error(
47
- `ltree label "${label}" contains invalid characters; allowed: alphanumeric, underscore, hyphen`,
48
- );
49
- }
50
- }
51
- }
21
+ export { assertValidLtree } from "./codecs-validation";
52
22
 
53
23
  export class LtreeCodec extends CodecImpl<
54
24
  typeof LTREE_CODEC_ID,
@@ -56,30 +26,34 @@ export class LtreeCodec extends CodecImpl<
56
26
  string,
57
27
  string
58
28
  > {
59
- async encode(value: string, _ctx: CodecCallContext): Promise<string> {
29
+ constructor(descriptor: AnyCodecDescriptor) {
30
+ super(descriptor, ltreeDataType);
31
+ }
32
+
33
+ async toWire(value: string, _ctx: CodecCallContext): Promise<string> {
60
34
  assertValidLtree(value);
61
35
  return value;
62
36
  }
63
37
 
64
- async decode(wire: string, _ctx: CodecCallContext): Promise<string> {
38
+ async fromWire(wire: string, _ctx: CodecCallContext): Promise<string> {
65
39
  return wire;
66
40
  }
67
41
 
68
- encodeJson(value: string): JsonValue {
42
+ toDataTypeValue(value: string): DataTypeValue {
69
43
  assertValidLtree(value);
70
- return value;
44
+ return this.dataTypeValueOf(value);
71
45
  }
72
46
 
73
- decodeJson(json: JsonValue): string {
74
- assertValidLtree(json);
75
- return json;
47
+ fromDataTypeValue(value: DataTypeValue): string {
48
+ return value.value as string;
76
49
  }
77
50
  }
78
51
 
79
52
  export class LtreeDescriptor extends PostgresCodecDescriptor<void> {
53
+ override readonly jsonType = "string" as const;
80
54
  protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr {
81
- // ltree's text form is already its canonical JSON string.
82
- return expression;
55
+ // JSON projections read through fromWire; cast so the wire value is text.
56
+ return CastExpr.as(expression, "text");
83
57
  }
84
58
  override readonly dataType = ltreeDataType.id;
85
59
  override readonly codecId = LTREE_CODEC_ID;
@@ -107,38 +81,41 @@ export class LtreeArrayCodec extends CodecImpl<
107
81
  readonly string[],
108
82
  readonly string[]
109
83
  > {
110
- async encode(value: readonly string[], _ctx: CodecCallContext): Promise<readonly string[]> {
84
+ constructor(descriptor: AnyCodecDescriptor) {
85
+ super(descriptor, ltreeArrayDataType);
86
+ }
87
+
88
+ async toWire(value: readonly string[], _ctx: CodecCallContext): Promise<readonly string[]> {
111
89
  for (const entry of value) {
112
90
  assertValidLtree(entry);
113
91
  }
114
92
  return value;
115
93
  }
116
94
 
117
- async decode(wire: readonly string[], _ctx: CodecCallContext): Promise<readonly string[]> {
95
+ async fromWire(wire: readonly string[], _ctx: CodecCallContext): Promise<readonly string[]> {
118
96
  return wire;
119
97
  }
120
98
 
121
- encodeJson(value: readonly string[]): JsonValue {
99
+ toDataTypeValue(value: readonly string[]): DataTypeValue {
122
100
  for (const entry of value) {
123
101
  assertValidLtree(entry);
124
102
  }
125
- return [...value];
103
+ return this.dataTypeValueOf([...value]);
126
104
  }
127
105
 
128
- decodeJson(json: JsonValue): readonly string[] {
106
+ fromDataTypeValue(value: DataTypeValue): readonly string[] {
107
+ const json = value.value;
129
108
  if (!Array.isArray(json)) {
130
109
  return [];
131
110
  }
132
- return json.map((entry) => {
133
- assertValidLtree(entry);
134
- return String(entry);
135
- });
111
+ return json.map((entry) => String(entry));
136
112
  }
137
113
  }
138
114
 
139
115
  export class LtreeArrayDescriptor extends PostgresCodecDescriptor<void> {
116
+ override readonly jsonType = "array" as const;
140
117
  protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr {
141
- // ltree[] text form projects as a JSON string array already.
118
+ // ltree[] already projects as a JSON string array for fromWire.
142
119
  return expression;
143
120
  }
144
121
  override readonly dataType = ltreeArrayDataType.id;
@@ -7,6 +7,17 @@ export const LTREE_ARRAY_STORAGE_TYPE = "ltreeArray" as const;
7
7
 
8
8
  export const LTREE_BASELINE_MIGRATION_NAME = "20260619T2142_install_ltree" as const;
9
9
 
10
+ export const LTREE_DECLARE_EXTENSION_MIGRATION_NAME =
11
+ "20261111T1905_declare_ltree_extension" as const;
12
+
13
+ export const LTREE_CREATE_EXTENSION_MIGRATION_NAME =
14
+ "20261111T1904_create_ltree_extension" as const;
15
+
16
+ export const LTREE_SEARCH_PATH_MIGRATION_NAME = "20261111T1908_find_ltree_on_search_path" as const;
17
+
18
+ export const LTREE_SEARCH_PATH_INSTALL_MIGRATION_NAME =
19
+ "20261111T1908_install_ltree_on_search_path" as const;
20
+
10
21
  export const LTREE_INVARIANTS = {
11
22
  installLtree: "ltree:install-ltree-v1",
12
23
  } as const;
@@ -2,28 +2,62 @@
2
2
  * Data types this extension owns.
3
3
  *
4
4
  * `ltree` and `ltree[]` are PostgreSQL types the target does not register.
5
- * A path's canonical form is its label text. An array's canonical form is a
5
+ * A path's stored form is its label text. An array's stored form is a
6
6
  * JSON array of those texts. Both accept text the contract can already write.
7
7
  * The texts are what a migration writes and what the catalog reports.
8
8
  */
9
9
 
10
- import type { DataType } from "@prisma/orm-framework/components/codec";
10
+ import type { DataType, DataTypeReader } from "@prisma/orm-framework/components/codec";
11
+ import { refuseJsonValue } from "@prisma/orm-framework/components/codec";
11
12
  import { sqlDataType } from "@prisma/orm-family-sql/contract/data-type";
12
13
  import { pgText } from "@prisma/orm-target-postgres/target/data-types";
14
+ import { assertValidLtree } from "./codecs-validation";
15
+
16
+ const readLtree: DataTypeReader = (json) => {
17
+ if (typeof json !== "string") {
18
+ return refuseJsonValue("ltree/ltree", "an ltree path string", json);
19
+ }
20
+ try {
21
+ assertValidLtree(json);
22
+ } catch {
23
+ return refuseJsonValue("ltree/ltree", "a valid ltree path string", json);
24
+ }
25
+ return json;
26
+ };
13
27
 
14
28
  export const ltreeDataType = sqlDataType("ltree/ltree", {
29
+ read: readLtree,
15
30
  texts: [{ text: "ltree", written: true, catalog: true }],
16
31
  casts: {
17
32
  [pgText.id]: (value) => value,
18
33
  },
19
34
  });
20
35
 
36
+ const readLtreeArray: DataTypeReader = (json) => {
37
+ const expected = "an array of ltree path strings";
38
+ if (!Array.isArray(json)) {
39
+ return refuseJsonValue("ltree/ltree-array", expected, json);
40
+ }
41
+ for (const entry of json) {
42
+ if (typeof entry !== "string") {
43
+ return refuseJsonValue("ltree/ltree-array", expected, entry);
44
+ }
45
+ try {
46
+ assertValidLtree(entry);
47
+ } catch {
48
+ return refuseJsonValue("ltree/ltree-array", expected, entry);
49
+ }
50
+ }
51
+ return json;
52
+ };
53
+
21
54
  /**
22
55
  * `sqlDataType` accepts parentheses in a type name and refuses square brackets.
23
56
  * PostgreSQL writes this type as `ltree[]`, and `format_type` reports `ltree[]`.
24
57
  * Declare the type first, then set that text so a migration and a catalog check use it.
25
58
  */
26
59
  const ltreeArrayDeclared = sqlDataType("ltree/ltree-array", {
60
+ read: readLtreeArray,
27
61
  listCast: {
28
62
  of: [pgText.id],
29
63
  cast: (elements) => [...elements],
@@ -11,6 +11,7 @@ import type { QueryOperationTypes } from "../types/operation-types";
11
11
  import { ltreeAuthoringTypes } from "./authoring";
12
12
  import { LTREE_ARRAY_CODEC_ID, LTREE_CODEC_ID } from "./constants";
13
13
  import { ltreeDataTypes } from "./data-types";
14
+ import { assertPatternArg } from "./pattern-validation";
14
15
  import { ltreeCodecRegistry } from "./registry";
15
16
 
16
17
  type CodecTypesBase = Record<string, { readonly input: unknown; readonly output: unknown }>;
@@ -57,8 +58,10 @@ export function ltreeQueryOperations<CT extends CodecTypesBase>(): QueryOperatio
57
58
  // Hierarchy operators compare two `ltree` values; the user argument shares the
58
59
  // column's `pg/ltree@1` codec. Pattern-match operators take a query/pattern
59
60
  // string (or `string[]`) that is NOT an ltree — it is bound as text and cast
60
- // in-template to `lquery` / `lquery[]` / `ltxtquery` (the validated-string-param
61
- // approach; verified executable under PGlite).
61
+ // in-template to `lquery` / `lquery[]` / `ltxtquery`. Literal string args get a
62
+ // lightweight structural check first (see `pattern-validation.ts`) so empty /
63
+ // obviously illegal patterns fail with a clear pack error instead of an opaque
64
+ // Postgres cast failure; Expression / ParamRef binds still defer to Postgres.
62
65
  const hierarchyOp = (
63
66
  method: string,
64
67
  operator: string,
@@ -80,11 +83,12 @@ export function ltreeQueryOperations<CT extends CodecTypesBase>(): QueryOperatio
80
83
  const patternOp = (
81
84
  method: string,
82
85
  operator: string,
83
- castType: string,
86
+ castType: "lquery" | "ltxtquery" | "lquery[]",
84
87
  self: CodecExpression<typeof LTREE_CODEC_ID, boolean, CT>,
85
88
  arg: unknown,
86
89
  many = false,
87
90
  ): BoolReturn => {
91
+ assertPatternArg(castType, arg);
88
92
  const selfCodec = codecOf(self);
89
93
  return buildOperation({
90
94
  method,
@@ -118,6 +122,34 @@ export function ltreeQueryOperations<CT extends CodecTypesBase>(): QueryOperatio
118
122
  lowering: { targetFamily: "sql", template },
119
123
  });
120
124
 
125
+ // Boolean operators on an `ltree[]` receiver (ADR-007). Same bind/cast rules as
126
+ // `firstMatchOp`, but the result is `boolean`. These are the operators
127
+ // `gist__ltree_ops` indexes (`<@`, `~`, `@`, `?`), plus `@>` which PostgreSQL
128
+ // ships and the opclass does not index.
129
+ const arrayBoolOp = (
130
+ method: string,
131
+ operator: string,
132
+ self: CodecExpression<typeof LTREE_ARRAY_CODEC_ID, boolean, CT>,
133
+ arg: unknown,
134
+ argCodecId: typeof LTREE_CODEC_ID | typeof TEXT_CODEC_ID,
135
+ castType?: "lquery" | "ltxtquery" | "lquery[]",
136
+ many = false,
137
+ ): BoolReturn => {
138
+ if (castType) assertPatternArg(castType, arg);
139
+ const template = castType
140
+ ? `{{self}} ${operator} ({{arg0}})::${castType}`
141
+ : `{{self}} ${operator} {{arg0}}`;
142
+ return buildOperation({
143
+ method,
144
+ args: [
145
+ toExpr(self, codecOf(self)),
146
+ toExpr(arg, many ? { codecId: argCodecId, many: true } : { codecId: argCodecId }),
147
+ ],
148
+ returns: BOOL_RETURN,
149
+ lowering: { targetFamily: "sql", template },
150
+ });
151
+ };
152
+
121
153
  // Tier 3 — first-match operators on an `ltree[]` receiver (ADR-003).
122
154
  const firstMatchOp = (
123
155
  method: string,
@@ -125,8 +157,9 @@ export function ltreeQueryOperations<CT extends CodecTypesBase>(): QueryOperatio
125
157
  self: CodecExpression<typeof LTREE_ARRAY_CODEC_ID, boolean, CT>,
126
158
  arg: unknown,
127
159
  argCodecId: typeof LTREE_CODEC_ID | typeof TEXT_CODEC_ID,
128
- castType?: string,
160
+ castType?: "lquery" | "ltxtquery",
129
161
  ): LtreeReturn => {
162
+ if (castType) assertPatternArg(castType, arg);
130
163
  const selfCodec = codecOf(self);
131
164
  const template = castType
132
165
  ? `{{self}} ${operator} ({{arg0}})::${castType}`
@@ -281,6 +314,33 @@ export function ltreeQueryOperations<CT extends CodecTypesBase>(): QueryOperatio
281
314
  impl: (self, query) =>
282
315
  firstMatchOp("firstMatchLtxtquery", "?@", self, query, TEXT_CODEC_ID, "ltxtquery"),
283
316
  },
317
+ // Boolean array operators (ADR-007). Distinct names from scalar methods because
318
+ // the operation registry keys by name only. `containsDescendantOf`,
319
+ // `matchesAnyLquery`, `matchesAnyLtxtquery`, and `matchesAnyLqueryArray` are
320
+ // in `gist__ltree_ops`; `containsAncestorOf` is not.
321
+ containsAncestorOf: {
322
+ self: { codecId: LTREE_ARRAY_CODEC_ID },
323
+ impl: (self, other) => arrayBoolOp("containsAncestorOf", "@>", self, other, LTREE_CODEC_ID),
324
+ },
325
+ containsDescendantOf: {
326
+ self: { codecId: LTREE_ARRAY_CODEC_ID },
327
+ impl: (self, other) => arrayBoolOp("containsDescendantOf", "<@", self, other, LTREE_CODEC_ID),
328
+ },
329
+ matchesAnyLquery: {
330
+ self: { codecId: LTREE_ARRAY_CODEC_ID },
331
+ impl: (self, pattern) =>
332
+ arrayBoolOp("matchesAnyLquery", "~", self, pattern, TEXT_CODEC_ID, "lquery"),
333
+ },
334
+ matchesAnyLqueryArray: {
335
+ self: { codecId: LTREE_ARRAY_CODEC_ID },
336
+ impl: (self, patterns) =>
337
+ arrayBoolOp("matchesAnyLqueryArray", "?", self, patterns, TEXT_CODEC_ID, "lquery[]", true),
338
+ },
339
+ matchesAnyLtxtquery: {
340
+ self: { codecId: LTREE_ARRAY_CODEC_ID },
341
+ impl: (self, query) =>
342
+ arrayBoolOp("matchesAnyLtxtquery", "@", self, query, TEXT_CODEC_ID, "ltxtquery"),
343
+ },
284
344
  // `lca(ltree[])` — array-receiver form (ADR-001, ADR-005). Named `lcaAll`
285
345
  // because prisma-next keys operations by name only (`createOperationRegistry`
286
346
  // throws on a duplicate); `lca` is already the variadic scalar method on
@@ -0,0 +1,111 @@
1
+ import type {
2
+ CollectionMethodBody,
3
+ CollectionMethodContribution,
4
+ CollectionMethodContributionId,
5
+ CollectionMethodContributionType,
6
+ CollectionMethodModelType,
7
+ } from "@prisma/orm-family-sql/relational-core/collection-methods";
8
+ import { blindCast } from "@prisma/orm-framework/utils/casts";
9
+ import { InternalError } from "@prisma/orm-framework/utils/internal-error";
10
+ import type { Ltree } from "../types/codec-types";
11
+ import { LTREE_CODEC_ID } from "./constants";
12
+
13
+ type LtreeFieldNames<Fields> = {
14
+ [Name in keyof Fields]: Fields[Name] extends { readonly many: object }
15
+ ? never
16
+ : Fields[Name] extends {
17
+ readonly type: { readonly codecId: typeof LTREE_CODEC_ID };
18
+ }
19
+ ? Name
20
+ : never;
21
+ }[keyof Fields];
22
+
23
+ type HierarchyMethodTable<Model extends CollectionMethodModelType> = {
24
+ readonly [Name in LtreeFieldNames<Model["fields"]> & string as `${Name}_descendants`]: {
25
+ readonly parameters: readonly [root: Ltree];
26
+ readonly filters: true;
27
+ readonly orders: false;
28
+ };
29
+ } & {
30
+ readonly [Name in LtreeFieldNames<Model["fields"]> & string as `${Name}_ancestors`]: {
31
+ readonly parameters: readonly [leaf: Ltree];
32
+ readonly filters: true;
33
+ readonly orders: false;
34
+ };
35
+ };
36
+
37
+ /**
38
+ * One `{field}_descendants` / `{field}_ancestors` method per scalar `ltree`
39
+ * field. Descendants map to `isDescendantOf` (`<@`); ancestors map to
40
+ * `isAncestorOf` (`@>`). Both are inclusive of equality, matching PostgreSQL.
41
+ */
42
+ export interface LtreeHierarchyMethods extends CollectionMethodContributionType {
43
+ readonly methods: HierarchyMethodTable<this["model"]>;
44
+ }
45
+
46
+ declare module "@prisma/orm-family-sql/relational-core/collection-methods" {
47
+ interface CollectionMethodRegistry {
48
+ readonly "ltree/hierarchy": LtreeHierarchyMethods;
49
+ }
50
+ }
51
+
52
+ interface HierarchyField {
53
+ isDescendantOf(root: Ltree): unknown;
54
+ isAncestorOf(leaf: Ltree): unknown;
55
+ }
56
+
57
+ type LtreeFields = Readonly<Record<string, HierarchyField>>;
58
+
59
+ function fieldOf(model: unknown, field: string): HierarchyField {
60
+ const accessor = blindCast<
61
+ LtreeFields,
62
+ "an ltree field of the ORM model accessor has hierarchy operators"
63
+ >(model)[field];
64
+ if (accessor === undefined) {
65
+ throw new InternalError(`The model accessor has no ltree field "${field}".`);
66
+ }
67
+ return accessor;
68
+ }
69
+
70
+ function descendantsMethod(field: string): CollectionMethodBody {
71
+ return (collection, ...args) => {
72
+ const [root] = blindCast<readonly [Ltree], "the type half declares a root path">(args);
73
+ return collection.where((model) => fieldOf(model, field).isDescendantOf(root));
74
+ };
75
+ }
76
+
77
+ function ancestorsMethod(field: string): CollectionMethodBody {
78
+ return (collection, ...args) => {
79
+ const [leaf] = blindCast<readonly [Ltree], "the type half declares a leaf path">(args);
80
+ return collection.where((model) => fieldOf(model, field).isAncestorOf(leaf));
81
+ };
82
+ }
83
+
84
+ function isScalarLtreeField(field: unknown): boolean {
85
+ if (typeof field !== "object" || field === null) return false;
86
+ const record = field as {
87
+ readonly many?: unknown;
88
+ readonly type?: { readonly kind?: string; readonly codecId?: string };
89
+ };
90
+ return (
91
+ record.many === undefined &&
92
+ record.type?.kind === "scalar" &&
93
+ record.type.codecId === LTREE_CODEC_ID
94
+ );
95
+ }
96
+
97
+ /** The run-time half of `LtreeHierarchyMethods`. */
98
+ export const ltreeHierarchyMethods: CollectionMethodContribution = {
99
+ id: "ltree/hierarchy" satisfies CollectionMethodContributionId,
100
+ methodsFor: (model) =>
101
+ Object.fromEntries(
102
+ Object.entries(model.fields).flatMap(([name, field]) =>
103
+ isScalarLtreeField(field)
104
+ ? [
105
+ [`${name}_descendants`, descendantsMethod(name)],
106
+ [`${name}_ancestors`, ancestorsMethod(name)],
107
+ ]
108
+ : [],
109
+ ),
110
+ ),
111
+ };