@ensnode/ensdb-sdk 1.13.0 → 1.14.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.
@@ -1,15 +1,16 @@
1
1
  import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core';
2
2
  import * as drizzle_orm from 'drizzle-orm';
3
3
  import * as ponder from 'ponder';
4
- import { DomainId, RegistryId, InterpretedLabel, PermissionsId, PermissionsResourceId, PermissionsUserId, RegistrationId, RenewalId, ResolverId, ResolverRecordsId, InterpretedName } from 'enssdk';
4
+ import { DomainId, RegistryId, InterpretedName, LabelHashPath, InterpretedLabel, PermissionsId, PermissionsResourceId, PermissionsUserId, RegistrationId, RenewalId, ResolverId, ResolverRecordsId } from 'enssdk';
5
5
 
6
6
  /**
7
7
  * The ENSv2 Schema
8
8
  *
9
9
  * While the initial approach was a highly materialized view of the ENS protocol, abstracting away
10
10
  * as many on-chain details as possible, in practice—due to the sheer complexity of the protocol at
11
- * resolution-time—it becomes more or less impossible to appropriately materialize the canonical
12
- * namegraph.
11
+ * resolution-time—full materialization of resolution behavior is impractical. The canonical
12
+ * nametree, however, is materialized inline via synchronous handler-side cascades; see
13
+ * `Domain.canonical*` fields and `canonicality-db-helpers.ts`.
13
14
  *
14
15
  * As a result, this schema takes a balanced approach. It mimics on-chain state as closely as possible,
15
16
  * with the obvious exception of materializing specific state that must trivially filterable. Then,
@@ -24,7 +25,7 @@ import { DomainId, RegistryId, InterpretedLabel, PermissionsId, PermissionsResou
24
25
  * in ENSApi. The current obvious exception is that `domain.ownerId` for ENSv1 Domains is the
25
26
  * _materialized_ _effective_ owner. ENSv1 includes a diverse number of ways to 'own' a domain,
26
27
  * including the ENSv1 Registry, various Registrars, and the NameWrapper. The ENSv1 indexing logic
27
- * within this ENSv2 plugin materializes the effective owner to simplify this aspect of ENS and
28
+ * within this Unigraph plugin materializes the effective owner to simplify this aspect of ENS and
28
29
  * enable efficient queries against `domain.ownerId`.
29
30
  *
30
31
  * When necessary, all datamodels are shared or polymorphic between ENSv1 and ENSv2, including
@@ -34,10 +35,12 @@ import { DomainId, RegistryId, InterpretedLabel, PermissionsId, PermissionsResou
34
35
  * guarantees (for example, ENSv1 BaseRegistrar Registrations may have a gracePeriod, but ENSv2
35
36
  * Registry Registrations do not).
36
37
  *
37
- * Instead of materializing a Domain's name at any point, we maintain an internal rainbow table of
38
- * labelHash -> InterpretedLabel (the Label entity). This ensures that regardless of how or when a
39
- * new label is encountered onchain, all Domains that use that label are automatically healed at
40
- * resolution-time.
38
+ * The `Label` entity (labelHash InterpretedLabel) remains the source of truth for label values.
39
+ * Canonical-tree fields on `Domain` (`canonicalName`, `canonicalLabelHashPath`, `canonicalPath`,
40
+ * `canonicalDepth`, `canonicalNode`) are materialized inline by the handlers in
41
+ * `canonicality-db-helpers.ts`. Label heals propagate to `canonicalName` via a GIN-indexed bulk
42
+ * UPDATE outside Ponder's cache; cascade round-trips are bounded to events that already pay a
43
+ * flush (canonicality flip, heal of an unknown label).
41
44
  *
42
45
  * ENSv1 and ENSv2 both fit the Registry → Domain → (Sub)Registry → Domain → ... namegraph model.
43
46
  * For ENSv1, each domain that has children implicitly owns a "virtual" Registry (a row of type
@@ -55,7 +58,7 @@ import { DomainId, RegistryId, InterpretedLabel, PermissionsId, PermissionsResou
55
58
  * for fresh ENSv1 virtual registries on first wire-up) or a single recursive-CTE batch UPDATE
56
59
  * otherwise (see `canonicality-db-helpers.ts`).
57
60
  *
58
- * Note also that the Protocol Acceleration plugin is a hard requirement for the ENSv2 plugin. This
61
+ * Note also that the Protocol Acceleration plugin is a hard requirement for the Unigraph plugin. This
59
62
  * allows us to rely on the shared logic for indexing:
60
63
  * a) ENSv1RegistryOld -> ENSv1Registry migration status
61
64
  * b) Domain-Resolver Relations for both ENSv1 and ENSv2 Domains
@@ -964,6 +967,152 @@ declare const domain: ponder.OnchainTable<{
964
967
  identity: undefined;
965
968
  generated: undefined;
966
969
  }, {}, {}>;
970
+ canonicalName: ponder.PgColumn<{
971
+ name: "canonicalName";
972
+ tableName: "domains";
973
+ dataType: "string";
974
+ columnType: "PgText";
975
+ data: InterpretedName;
976
+ driverParam: string;
977
+ notNull: false;
978
+ hasDefault: false;
979
+ isPrimaryKey: false;
980
+ isAutoincrement: false;
981
+ hasRuntimeDefault: false;
982
+ enumValues: [string, ...string[]];
983
+ baseColumn: never;
984
+ identity: undefined;
985
+ generated: undefined;
986
+ }, {}, {
987
+ $type: InterpretedName;
988
+ }>;
989
+ canonicalLabelHashPath: ponder.PgColumn<{
990
+ name: "canonicalLabelHashPath";
991
+ tableName: "domains";
992
+ dataType: "array";
993
+ columnType: "PgArray";
994
+ data: LabelHashPath;
995
+ driverParam: string | string[];
996
+ notNull: false;
997
+ hasDefault: false;
998
+ isPrimaryKey: false;
999
+ isAutoincrement: false;
1000
+ hasRuntimeDefault: false;
1001
+ enumValues: undefined;
1002
+ baseColumn: drizzle_orm.Column<{
1003
+ name: "";
1004
+ tableName: "domains";
1005
+ dataType: "string";
1006
+ columnType: "PgHex";
1007
+ data: `0x${string}`;
1008
+ driverParam: string;
1009
+ notNull: false;
1010
+ hasDefault: false;
1011
+ isPrimaryKey: false;
1012
+ isAutoincrement: false;
1013
+ hasRuntimeDefault: false;
1014
+ enumValues: undefined;
1015
+ baseColumn: never;
1016
+ identity: undefined;
1017
+ generated: undefined;
1018
+ }, {}, {}>;
1019
+ identity: undefined;
1020
+ generated: undefined;
1021
+ }, {}, {
1022
+ size: undefined;
1023
+ baseBuilder: ponder.PgColumnBuilder<{
1024
+ name: "";
1025
+ dataType: "string";
1026
+ columnType: "PgHex";
1027
+ data: `0x${string}`;
1028
+ driverParam: string;
1029
+ enumValues: undefined;
1030
+ generated: undefined;
1031
+ }, {}, {
1032
+ generated: undefined;
1033
+ }, drizzle_orm.ColumnBuilderExtraConfig>;
1034
+ $type: LabelHashPath;
1035
+ }>;
1036
+ canonicalPath: ponder.PgColumn<{
1037
+ name: "canonicalPath";
1038
+ tableName: "domains";
1039
+ dataType: "array";
1040
+ columnType: "PgArray";
1041
+ data: DomainId[];
1042
+ driverParam: string | string[];
1043
+ notNull: false;
1044
+ hasDefault: false;
1045
+ isPrimaryKey: false;
1046
+ isAutoincrement: false;
1047
+ hasRuntimeDefault: false;
1048
+ enumValues: [string, ...string[]];
1049
+ baseColumn: drizzle_orm.Column<{
1050
+ name: "";
1051
+ tableName: "domains";
1052
+ dataType: "string";
1053
+ columnType: "PgText";
1054
+ data: string;
1055
+ driverParam: string;
1056
+ notNull: false;
1057
+ hasDefault: false;
1058
+ isPrimaryKey: false;
1059
+ isAutoincrement: false;
1060
+ hasRuntimeDefault: false;
1061
+ enumValues: [string, ...string[]];
1062
+ baseColumn: never;
1063
+ identity: undefined;
1064
+ generated: undefined;
1065
+ }, {}, {}>;
1066
+ identity: undefined;
1067
+ generated: undefined;
1068
+ }, {}, {
1069
+ size: undefined;
1070
+ baseBuilder: ponder.PgColumnBuilder<{
1071
+ name: "";
1072
+ dataType: "string";
1073
+ columnType: "PgText";
1074
+ data: string;
1075
+ enumValues: [string, ...string[]];
1076
+ driverParam: string;
1077
+ }, {}, {}, drizzle_orm.ColumnBuilderExtraConfig>;
1078
+ $type: DomainId[];
1079
+ }>;
1080
+ canonicalDepth: ponder.PgColumn<{
1081
+ name: "canonicalDepth";
1082
+ tableName: "domains";
1083
+ dataType: "number";
1084
+ columnType: "PgInteger";
1085
+ data: number;
1086
+ driverParam: string | number;
1087
+ notNull: false;
1088
+ hasDefault: false;
1089
+ isPrimaryKey: false;
1090
+ isAutoincrement: false;
1091
+ hasRuntimeDefault: false;
1092
+ enumValues: undefined;
1093
+ baseColumn: never;
1094
+ identity: undefined;
1095
+ generated: undefined;
1096
+ }, {}, {}>;
1097
+ canonicalNode: ponder.PgColumn<{
1098
+ name: "canonicalNode";
1099
+ tableName: "domains";
1100
+ dataType: "string";
1101
+ columnType: "PgHex";
1102
+ data: `0x${string}`;
1103
+ driverParam: string;
1104
+ notNull: false;
1105
+ hasDefault: false;
1106
+ isPrimaryKey: false;
1107
+ isAutoincrement: false;
1108
+ hasRuntimeDefault: false;
1109
+ enumValues: undefined;
1110
+ baseColumn: never;
1111
+ identity: undefined;
1112
+ generated: undefined;
1113
+ }, {}, {
1114
+ $type: `0x${string}`;
1115
+ }>;
967
1116
  };
968
1117
  extra: {
969
1118
  byType: drizzle_orm_pg_core.IndexBuilder;
@@ -971,6 +1120,11 @@ declare const domain: ponder.OnchainTable<{
971
1120
  bySubregistry: drizzle_orm_pg_core.IndexBuilder;
972
1121
  byOwner: drizzle_orm_pg_core.IndexBuilder;
973
1122
  byLabelHash: drizzle_orm_pg_core.IndexBuilder;
1123
+ byCanonicalNameExact: drizzle_orm_pg_core.IndexBuilder;
1124
+ byCanonicalNameFuzzy: drizzle_orm_pg_core.IndexBuilder;
1125
+ byCanonicalLabelHashPath: drizzle_orm_pg_core.IndexBuilder;
1126
+ byCanonicalNode: drizzle_orm_pg_core.IndexBuilder;
1127
+ byCanonicalDepth: drizzle_orm_pg_core.IndexBuilder;
974
1128
  };
975
1129
  dialect: "pg";
976
1130
  }>;
@@ -1047,7 +1201,7 @@ declare const registration: ponder.OnchainTable<{
1047
1201
  tableName: "registrations";
1048
1202
  dataType: "string";
1049
1203
  columnType: "PgEnumColumn";
1050
- data: "BaseRegistrar" | "NameWrapper" | "ThreeDNS" | "ENSv2RegistryRegistration" | "ENSv2RegistryReservation";
1204
+ data: "NameWrapper" | "BaseRegistrar" | "ThreeDNS" | "ENSv2RegistryRegistration" | "ENSv2RegistryReservation";
1051
1205
  driverParam: string;
1052
1206
  notNull: true;
1053
1207
  hasDefault: false;
@@ -1918,7 +2072,8 @@ declare const label: ponder.OnchainTable<{
1918
2072
  }>;
1919
2073
  };
1920
2074
  extra: {
1921
- byInterpreted: drizzle_orm_pg_core.IndexBuilder;
2075
+ byInterpretedExact: drizzle_orm_pg_core.IndexBuilder;
2076
+ byInterpretedFuzzy: drizzle_orm_pg_core.IndexBuilder;
1922
2077
  };
1923
2078
  dialect: "pg";
1924
2079
  }>;
@@ -1957,7 +2112,7 @@ declare const label_relations: drizzle_orm.Relations<"labels", {
1957
2112
  * pattern matcher recover the key from event args. See the helper module's block comment for the
1958
2113
  * full rationale.
1959
2114
  *
1960
- * The ensv2 plugin depends on the Protocol Acceleration plugin in order to piggyback on this
2115
+ * The Unigraph plugin depends on the Protocol Acceleration plugin in order to piggyback on this
1961
2116
  * Registry migration logic.
1962
2117
  */
1963
2118
  declare const migratedNodeByParent: ponder.OnchainTable<{
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { EnsDbPublicConfig, IndexingMetadataContext, IndexingMetadataContextInitialized, Unvalidated } from '@ensnode/ensnode-sdk';
2
2
  export { IndexingMetadataContext, IndexingMetadataContextInitialized, IndexingMetadataContextStatusCodes, IndexingMetadataContextUninitialized } from '@ensnode/ensnode-sdk';
3
3
  import { NodePgDatabase } from 'drizzle-orm/node-postgres';
4
- import { a as abstractEnsIndexerSchema } from './index-CVAtPcO6.js';
4
+ import { a as abstractEnsIndexerSchema } from './index-DdrIzPaZ.js';
5
5
  import { e as ensNodeSchema } from './index-BHoa233Z.js';
6
6
  import 'drizzle-orm/pg-core';
7
7
  import 'drizzle-orm';
package/dist/index.js CHANGED
@@ -264,7 +264,43 @@ var domain = onchainTable(
264
264
  // If this is an ENSv1Domain, may have a `rootRegistryOwner`, otherwise null.
265
265
  rootRegistryOwnerId: t.hex().$type(),
266
266
  // Whether this Domain is part of the canonical nametree. Mirrors the parent Registry's flag.
267
- canonical: t.boolean().notNull().default(false)
267
+ canonical: t.boolean().notNull().default(false),
268
+ /**
269
+ * Materialized Canonical Name, NULL iff `canonical = false`.
270
+ * Maintained by `canonicality-db-helpers.ts`.
271
+ *
272
+ * @example "vitalik.eth"
273
+ */
274
+ canonicalName: t.text().$type(),
275
+ /**
276
+ * Materialized Canonical LabelHashPath, NULL iff `canonical = false`.
277
+ * Maintained by `canonicality-db-helpers.ts`.
278
+ *
279
+ * @dev Note that LabelHashPaths are in traversal-order (i.e. [labelhash("eth"), labelhash("vitalik")]).
280
+ */
281
+ canonicalLabelHashPath: t.hex().array().$type(),
282
+ /**
283
+ * Materialized Canonical Domain Path, NULL iff `canonical = false`.
284
+ * Maintained by `canonicality-db-helpers.ts`.
285
+ *
286
+ * @dev Note that canonicalPath is in traversal-order (i.e. ["eth"'s DomainId, "vitalik"'s DomainId]).
287
+ */
288
+ canonicalPath: t.text().array().$type(),
289
+ /**
290
+ * Materialized Canonical Depth, NULL iff `canonical = false`.
291
+ * Maintained by `canonicality-db-helpers.ts`.
292
+ *
293
+ * @dev The depth of this Domain in the Canonical Nametree i.e. the number of Labels in its Canonical Name.
294
+ * @example "eth" depth 1, "vitalik.eth" depth 2, etc
295
+ */
296
+ canonicalDepth: t.integer(),
297
+ /**
298
+ * Materialized Canonical Node, NULL iff `canonical = false`.
299
+ * Maintained by `canonicality-db-helpers.ts`.
300
+ *
301
+ * @dev the computed Node (via `namehash`) of this Domain's Canonical Name.
302
+ */
303
+ canonicalNode: t.hex().$type()
268
304
  // NOTE: Domain-Resolver Relations tracked via Protocol Acceleration plugin
269
305
  }),
270
306
  (t) => ({
@@ -272,7 +308,18 @@ var domain = onchainTable(
272
308
  byRegistry: index().on(t.registryId),
273
309
  bySubregistry: index().on(t.subregistryId).where(sql`${t.subregistryId} IS NOT NULL`),
274
310
  byOwner: index().on(t.ownerId),
275
- byLabelHash: index().on(t.labelHash)
311
+ byLabelHash: index().on(t.labelHash),
312
+ // hash index avoids the btree 8191-byte row-size hazard for spam names
313
+ byCanonicalNameExact: index().using("hash", t.canonicalName),
314
+ // GIN trigram index for substring / similarity queries (inline `gin_trgm_ops` via `sql`
315
+ // because passing it through `.op()` gets dropped by Ponder)
316
+ byCanonicalNameFuzzy: index().using("gin", sql`${t.canonicalName} gin_trgm_ops`),
317
+ // GIN containment for `cascadeLabelHeal`'s `canonical_label_hash_path @> ARRAY[lh]` lookup
318
+ byCanonicalLabelHashPath: index().using("gin", t.canonicalLabelHashPath),
319
+ // hash index for resolver-record → canonical-domain joins
320
+ byCanonicalNode: index().using("hash", t.canonicalNode),
321
+ // btree for ORDER BY canonical_depth (typeahead and DEPTH-ordered browse)
322
+ byCanonicalDepth: index().on(t.canonicalDepth)
276
323
  })
277
324
  );
278
325
  var relations_domain = relations(domain, ({ one, many }) => ({
@@ -520,7 +567,12 @@ var label = onchainTable(
520
567
  interpreted: t.text().notNull().$type()
521
568
  }),
522
569
  (t) => ({
523
- byInterpreted: index().on(t.interpreted)
570
+ // hash index avoids the btree 8191-byte row-size hazard for spam labels (a single label can
571
+ // exceed btree's leaf-size limit)
572
+ byInterpretedExact: index().using("hash", t.interpreted),
573
+ // GIN trigram index for substring / similarity queries (inline `gin_trgm_ops` via `sql`
574
+ // because passing it through `.op()` gets dropped by Ponder)
575
+ byInterpretedFuzzy: index().using("gin", sql`${t.interpreted} gin_trgm_ops`)
524
576
  })
525
577
  );
526
578
  var label_relations = relations(label, ({ many }) => ({