Overview
Reference for @seedprotocol/query: queryBySchema, getSeed, getSeeds, getSeedAttestations, pinned reads and the other exports, the SeedRecord shape, paging and UID prefixes, schema names, caching and frozen records, and the environment variables it reads.
@seedprotocol/query reads published Seed items as JSON, without the SDK's local database, a wallet or a schema file. For each Seed it takes the latest Version, or the one a pinned read names, then the newest attestation of each property, expands relations and fetches Html bodies stored on Arweave. @seedprotocol/feed builds on it, and the SDK uses it for getPublishedSeedRecord.
import { queryBySchema, getSeed, getSeeds, getSeedAttestations, type SeedRecord } from '@seedprotocol/query'For a walkthrough, see Read published data. Data sources (remote, local, auto) are on Query adapters.
Install
npm install @seedprotocol/queryIt brings @seedprotocol/eas (attestation reads) and @seedprotocol/arweave (content reads) with it, pinned to its own exact version. On Node it needs 20.18.1 or later.
| Entry | Resolves to | Adds |
|---|---|---|
@seedprotocol/query in Node and Bun | dist/index.node.js | A disk cache (FileCache) under CACHE_DIR |
@seedprotocol/query in a browser bundle (browser condition) | dist/index.js | Memory cache only, no fs |
@seedprotocol/query/node | dist/node.js | Only initializeQueryPlatform, teardownQueryPlatform, isQueryPlatformInitialized and FileCache |
The main entry exports the same API in both builds; Query adapters lists what differs.
queryBySchema
Lists a schema's published items, newest first by Seed attestation time.
function queryBySchema(
schemaName: string,
options?: QueryBySchemaOptions,
): Promise<QueryBySchemaResult>Options
| Name | Type | Default | Description |
|---|---|---|---|
limit | number | 100 | Seeds to list. Seeds with no Version from their attester, or only revoked ones, are dropped after listing, so a page can hold fewer items than limit, even none, while later pages still have more. listed says when the listing has ended |
skip | number | 0 | Seeds to skip, for paging |
uidPrefix | string | none | Only Seeds whose UID starts with this. See UID prefixes |
attesters | readonly string[] | none | Only Seeds attested by one of these accounts. See One publisher's items |
relations | 'expand' | 'uids' | 'expand' | 'expand' replaces each relation's UID with the related item's values, or an Html or File relation's with its Arweave URL on the first read gateway (https://arweave.net/raw/<txId> by default). 'uids' leaves every relation a UID and reads no related items, which saves their requests. Either way a relation comes back under the same key. Any other value throws a TypeError before any request |
hydrateStorage | boolean | true | Fetch Html bodies and other text stored on Arweave into the value. false leaves gateway URLs and skips those requests |
cache | boolean | The CACHE_ENABLED setting | false skips the cache for this call. Only remote reads use the cache |
source | 'remote' | 'local' | 'auto' | 'remote' | Where to read from. See Query adapters |
maxAge | number | CACHE_MAX_AGE, else 0 | Seconds after this process last checked the collection during which it's returned from the cache without any request. 0 checks on every call. Only where the collection cache applies |
relations replaced expandRelations in 0.7.9. An expandRelations option passed from untyped code is ignored, so relations come back expanded.
Returns
type QueryBySchemaResult = {
items: SeedRecord[]
limit: number
skip: number
listed: number // Seeds the listing returned for this page, before any were left out
etag?: string // only for a cached remote read of the first page (skip: 0, no uidPrefix or attesters)
}items is a new array each call, but the records in it may be the cache's own objects. See Frozen records.
items can be shorter than listed, so page on listed, not on items.length. listed < limit means the listing has ended. listed === limit doesn't promise more: when the last page is exactly full, the next one comes back with listed: 0.
What's left out:
- Revoked Seeds, Seeds with no Version from their attester, and Seeds whose Versions were all revoked.
- Seeds of other schemas, however similar the name.
- Seeds of other accounts, when you pass
attesters. Without it every account's Seeds are listed; see One publisher's items. - Versions not attested by the Seed's attester, and property attestations not attested by their Version's attester. Anyone can make an attestation that references your Seed or Version, so these are dropped before the latest Version and the newest value of each property are picked. Attesters are compared case-insensitively, and an attestation whose attester (or parent's attester) is unknown is kept.
getSeedand its changelog apply the same rule. - Unfilled references: a relation value that's a non-zero
bytes32below 2^32, which a publish left in a slot it never filled (isUnfilledReference). A relation holding one is absent, a list drops it and keeps its other members, and neither is read as a related Seed.getSeedandgetSeedsdo the same; changelogs andgetSeedAttestationskeep the value.
Example
import { queryBySchema } from '@seedprotocol/query'
const limit = 20
for (let skip = 0; ; skip += limit) {
const { items, listed } = await queryBySchema('post', { limit, skip })
for (const post of items) {
if (typeof post.data.title === 'string') console.log(post.seedUid, post.data.title)
}
if (listed < limit) break
}getSeed
Reads one item by its Seed UID, at its latest Version or a pinned one, optionally with its edit history.
function getSeed(seedUid: string, options?: GetSeedOptions): Promise<GetSeedResult | null>
type GetSeedResult = SeedRecord & { readonly changelog?: readonly ChangelogEntry[] }It returns null when the UID is empty, there's no such Seed, the Seed has no Version from its attester, the Seed has no schema name, every Version was revoked, or the Seed has no Version that matches the pin. The UID is matched in any case and the record's seedUid is lowercase.
Options
relations, hydrateStorage, cache and source work as for queryBySchema. Also:
| Name | Type | Default | Description |
|---|---|---|---|
versionUid | string | none | Read the Seed at this Version, in any case. See Pinned reads |
asOf | number | none | Read the Seed as it was at this Unix time (seconds). See Pinned reads |
include | 'data' | 'data+changelog' | 'changelog' | 'data' | 'data+changelog' adds changelog. 'changelog' returns only the history, with data: {} |
changelog.granularity | 'version' | 'property' | 'version' | One entry per Version, or one per property attestation |
changelog.since | number | none | Leave out entries before this Unix time (seconds) |
changelog.limit | number | none | Keep only the newest this many entries |
Pinned reads
versionUid and asOf read a Seed at one of its Versions instead of the latest:
versionUidalone: that Version, with the newest attestation of each property on it. Apatchpublish adds property attestations to the current Version, so this includes patches made after the Version was created.asOfalone: the newest Version created at or beforeasOf, with only the property attestations made by then.- Both: the named Version, if it was created by
asOf, with the property attestations made by then.
A revoked Version, a Version of another Seed, or one created after asOf returns null. Relations are expanded and storage is hydrated as for the latest Version, but related items are read at their latest Version. The changelog isn't pinned: 'data+changelog' returns pinned data with the full history, and 'changelog' ignores the pin.
import { getSeed } from '@seedprotocol/query'
const seedUid = '0x6e195761cf9fce0a81ad24e23dbafce37e2b2d2099357d634acafebc174778f6'
const asPublished = await getSeed(seedUid, { versionUid: '0xd76fcd99122c77e9f1f31e1dc16b4dfa66b8137533a04488f8e71b8ea928773d' })
const beforeTheEdit = await getSeed(seedUid, { asOf: Date.parse('2026-02-09T13:55:00Z') / 1000 })Changelog entries
Entries are in chronological order, oldest first, also after limit.
type VersionChangelogEntry = {
type: 'version'
at: number // Unix seconds
versionUid: string
before: Record<string, unknown> // the previous Version's values
after: Record<string, unknown> // this Version's values
changedKeys: string[] // sorted
}
type PropertyChangelogEntry = {
type: 'property'
at: number
versionUid: string
property: string // the key it has in data, e.g. 'html' or 'author'
attestationUid: string
previousValue: unknown
nextValue: unknown
}before and after use the same keys as data. A removed property shows as a change to undefined in property entries and is missing from a Version entry's after. Changelog values are flat: relations stay UIDs and Arweave text isn't fetched. Each value is a string, or an array of UIDs for a list of relations, in the same forms as data, and an unfilled reference stays in as a value. Property granularity also shows edits published as patches to the same Version, which version granularity can't.
Example
import { getSeed } from '@seedprotocol/query'
const post = await getSeed('0xfd8c50ca131b725c288be7f46e4abe0c7b06f1226e98f552642edde8280ad900', {
include: 'data+changelog',
changelog: { granularity: 'property', limit: 10 },
})
for (const entry of post?.changelog ?? []) {
if (entry.type === 'property') console.log(new Date(entry.at * 1000).toISOString(), entry.property)
}getSeeds
Reads many items by Seed UID, of any schemas, in one set of indexer requests instead of one per item.
function getSeeds(seedUids: readonly string[], options?: GetSeedsOptions): Promise<Map<string, SeedRecord>>
type GetSeedsOptions = AssembleOptions & { pins?: ReadonlyMap<string, VersionPin> }
type VersionPin = { versionUid?: string; asOf?: number }The map is keyed by each UID as you passed it, in input order. UIDs are matched in any case, so a repeated UID is read once, under its first spelling. Empty strings are skipped.
UIDs that getSeed would return null for are left out of the map: no such Seed, a revoked Seed, no Version from its attester, no schema name, every Version revoked, or no Version that matches the pin.
Options
relations, hydrateStorage, cache and source work as for queryBySchema. pins maps a Seed UID (in any case) to a { versionUid, asOf } pin, read as in Pinned reads; Seeds without a pin are read at their latest Version. There's no include, so for a changelog call getSeed.
getSeeds shares the item cache with getSeed: a record either one stored is returned to the other, and the cached Seeds in one call are checked for changes in a single request. Under source: 'auto', Seeds the local source doesn't have are read from EAS; see Query adapters.
Example
import { getSeeds } from '@seedprotocol/query'
const first = '0x6e195761cf9fce0a81ad24e23dbafce37e2b2d2099357d634acafebc174778f6'
const second = '0x2a34bcc7d384ee1a0999affae1b4712cb538ee0a1e237de3b748d3dba2395a53'
const posts = await getSeeds([first, second], {
pins: new Map([[first, { versionUid: '0xd76fcd99122c77e9f1f31e1dc16b4dfa66b8137533a04488f8e71b8ea928773d' }]]),
})
for (const [seedUid, post] of posts) console.log(seedUid, post.versionUid, post.data.title)getSeedAttestations
Reads many items with the property attestations they're assembled from, keeping the EAS name and EAS type that a SeedRecord doesn't. Use it when you need the on-chain types, for example to export items to another format.
function getSeedAttestations(
seedUids: readonly string[],
options?: Pick<GetSeedsOptions, 'source' | 'pins'>,
): Promise<Map<string, SeedAttestations>>
type SeedAttestations = {
readonly record: SeedRecord // assembled with relations: 'uids' and no storage hydration
readonly properties: readonly SeedPropertyAttestation[]
}
type SeedPropertyAttestation = {
readonly attestationUid: string
readonly schemaUid: string // the one-field EAS schema, '<easType> <easName>'
readonly easName: string // e.g. 'title', 'author_identity_ids'
readonly easType: string // e.g. 'string', 'bytes32', 'bytes32[]', 'bool', 'uint256'
readonly value: string | boolean | readonly string[]
readonly isRelation: boolean
readonly attester?: string
readonly timeCreated: number
}The map follows getSeeds: keyed by each UID as you passed it, with the UIDs getSeed would return null for left out. source and pins work as they do there. It never reads or writes the query cache.
properties holds the newest attestation of each property on the Version read. value is a decimal string for integer types, a boolean for bool, a string array for array types, and otherwise the attested string. isRelation is the rule data uses. A removed property is left out, but an unfilled reference stays, with isRelation: true, so check relation values with isUnfilledReference before you treat them as Seed UIDs.
import { getSeedAttestations } from '@seedprotocol/query'
const seedUid = '0xfd140023f3a01376f350e9e5b883c729139e9333827ac48af2f23d6591068d98'
const seeds = await getSeedAttestations([seedUid])
for (const property of seeds.get(seedUid)?.properties ?? []) {
console.log(property.easType, property.easName, property.isRelation)
}SeedRecord
type SeedRecord = {
readonly seedUid: string // the Seed attestation UID, the item's permanent ID
readonly schemaName: string // e.g. 'post'
readonly attester?: string // the account that attested the Seed (checksummed)
readonly timeCreated: number // Seed attestation time, Unix seconds
readonly versionUid: string // the Version the values come from
readonly data: SeedData // { readonly [key: string]: unknown }
}What's in data:
- The newest value of each property on the latest Version, or on the pinned one. A property whose newest attestation is the empty value,
''for astringor all zeros for abytes32, is left out: that's how a publisher removes a property. - Each property under its EAS name (
published_at), plus a camelCase copy (publishedAt) when that's different.dataalso repeatsseedUid,attester,timeCreatedandversionUid. - Relations under their schema key, in both
relationsmodes and in changelogs. A relation is abytes32orbytes32[]property; an attestation with no type counts when its EAS name ends in_idor_ids.author_idcomes back asauthor, and a list'sauthor_identity_idsasauthors, each with its camelCase copy when different (feature_image_idasfeature_imageandfeatureImage). Astring external_idisn't a relation, so it staysexternal_idandexternalId. A relation holding an unfilled reference is absent. - List keys from the first word of the EAS name, unless you set your models. A list's EAS name runs the singular key, the related model and
idstogether, so query takes the first word and makes it plural:co_author_identity_idscomes back ascos,featured_portfolio_clip_idsasfeatureds. With models set, they come back asco_authorsandcoAuthors, and asfeatured. - Relations expanded into the related item's values (with the default
relations: 'expand'); a list becomes an array of objects. Only relations expand, so astringvalue that equals a Seed's UID stays a string. - Html and other text stored on Arweave fetched into the value (with
hydrateStorage), from the first read gateway that has it. Arweave links inside the body (src,href,poster,srcset) are moved onto the first read gateway, so they load from the gateway this process reads from; see Arweave and gateways. - Marker keys
_feedFieldStorageModelsand_feedListElementStorageModels, which record which fields held Html, File or Json content. The feed package uses them to pick an item's body.
Apart from relations and lists, property values are strings as attested. An integer (uint8 to uint256, int8 to int256 on EAS) reads as its decimal string, exact at any size ('9007199254740993'). A Date is published as a uint256 of Unix seconds, so it reads as '1700000000', not an ISO date; the SDK converts it when it syncs, query doesn't. Any other non-string value is its JSON: a bool reads as 'true'.
Every app that publishes to a schema decides its own properties, so check a field's type before you use it.
When hydrateStorage can't fetch a body, it leaves the field as the gateway URL (https://arweave.net/raw/<txId> with the default gateway) without an error. It asks each read gateway in turn, skipping any that failed a read in the last 60 seconds unless all have, and moves on when a gateway doesn't answer, returns an error status or takes over 15 seconds. A 4xx other than 429 (a 404, say) only means that gateway doesn't have the body, so it doesn't count as a failed read; 5xx, 429, timeouts and network errors do. The body is requested in the stored link's form, {gateway}/raw/<txId> or {gateway}/<txId>. It stops without a body when a gateway answers with an image, video or audio content type, or with a body over 8 MB, empty, or containing a NUL character. With the cache on, the record keeps the URL until CACHE_TTL runs out. Arweave and gateways says how the read gateway list is built and how to change it.
Schema names
The schema name is the model's EAS name, as easName from @seedprotocol/eas builds it: Post is published under post, BlogPost under blog_post, HTMLPage under html_page. It's matched exactly and case-sensitively, so queryBySchema('Post') returns nothing. SDKs before 0.7.4 published names with acronyms or digits differently (HTMLPage as htmlpage), and those items are only found under the old name.
The name belongs to the model, not to your app: post returns every app's post items. The data model explains why.
UID prefixes
uidPrefix finds Seeds whose UID starts with some hex digits, for short links like /posts/fd8c50ca.
- At least 4 and at most 64 hex digits, with or without
0x, in any case. - Anything else returns no items and
listed: 0without a request. - Otherwise it filters like a normal listing (schema, not revoked) and pages with
limitandskip. It never uses the collection cache.
normalizeUidPrefix applies the same rules:
function normalizeUidPrefix(prefix: string): string | null
normalizeUidPrefix('FD8C50CA') // '0xfd8c50ca'
normalizeUidPrefix('0xfd8') // null: fewer than 4 digitsOne publisher's items
Without attesters, queryBySchema lists every account's Seeds of a schema; each Seed's values still come only from its own attester (see What's left out). Pass attesters to list only some accounts' Seeds:
import { queryBySchema } from '@seedprotocol/query'
const { items } = await queryBySchema('post', {
attesters: ['0xE870B14fCfF4A408e6eCb65Bd40f64d08859Efb3'],
limit: 20,
})Addresses are matched in any case, and duplicates count once. Entries that aren't addresses are dropped; if none is left, or the list is empty, you get no items and listed: 0 without a request. The filter applies before limit and skip, so other accounts' Seeds don't shorten a page, and it combines with uidPrefix. Like uidPrefix, it never uses the collection cache. normalizeAttesters(attesters) returns the list as query uses it (lowercase, 0x plus 40 hex digits, no duplicates), or null when no entry is an address.
To limit every read to one account, whatever the call passes, register a custom source, as in Query adapters.
queryBySchemaForMonth
Every item of a schema whose Seed was attested in one calendar month, up to 1,000. The feed package uses it for archives.
function queryBySchemaForMonth(
schemaName: string,
year: number,
month: number, // 1–12
options?: AssembleOptions, // relations, hydrateStorage, cache, source
): Promise<SeedRecord[]>The month runs from 00:00 UTC on its first day up to 00:00 UTC on the next month's first day, whatever the process's time zone. It writes items to the item cache but has no collection cache.
initializeQueryPlatform
Fills in whatever the process is missing to read remotely. Clients that are already registered, for example by a running SeedClient, are kept as they are. queryBySchema, getSeed, getSeeds and queryBySchemaForMonth call it themselves, so you only need it to choose the Arweave gateway or set models.
- EAS, in Node: registers
NodeEasClientfrom@seedprotocol/eas/nodeif no EAS client is registered, thenensureEasPlatform()fills the query-client slot if it's empty. - EAS, in the browser build:
ensureEasPlatform()registers the default fetch-based clients for whichever of the two client slots is empty. - Arweave, in Node: registers the Node Arweave client with the gateway below, if no Arweave client is registered.
- Arweave, in the browser build: only sets the preferred read gateway, if no Arweave client is registered. It doesn't load the Node clients.
arweaveDomain, when you pass it, always becomes the Arweave host, even when a client was already registered. It also locks the host: the read-gateway probe won't switch away from it, andARWEAVE_HOSTno longer overrides it.- One copy of each package: every call, including the ones the read functions make, throws
DuplicateEasModuleErrororDuplicateArweaveModuleErrorwhen another copy of@seedprotocol/easor@seedprotocol/arweaveregistered the clients, unlesssourceis'local'. See Troubleshooting.
function initializeQueryPlatform(options?: InitializeQueryPlatformOptions): Promise<void>
function teardownQueryPlatform(): Promise<void>
function isQueryPlatformInitialized(): boolean| Option | Default | Description |
|---|---|---|
arweaveDomain | NEXT_PUBLIC_ARWEAVE_HOST, ARWEAVE_HOST or VITE_ARWEAVE_HOST, else arweave.net | Gateway for Arweave bodies and URLs. The default only applies when query registers the Arweave client itself |
source | none | The source the caller reads from. 'local' skips the one-copy check. The read functions pass their own source |
models | none | Your schema file's models, as for setQueryModels. null clears them; leaving it out keeps what's set |
The first call does the setup, and later calls skip it, so a later arweaveDomain has no effect. models and the one-copy check are the exceptions: they apply on every call. teardownQueryPlatform clears the flag and the models, so the next call runs the setup again. Because query keeps a registered client, calling it after @seedprotocol/feed's initializeFeedPlatform({ arweaveDomain }) keeps feed's gateway.
setQueryModels
Gives query your models, so List relations come back under their schema keys instead of the first word of their EAS name. initializeQueryPlatform({ models }) does the same.
function setQueryModels(models: QueryModels | null): voidQueryModels takes the models of a schema file imported as JSON:
import { initializeQueryPlatform } from '@seedprotocol/query'
import schema from './seed.schema.json'
await initializeQueryPlatform({ models: schema.models })A value typed SchemaFileFormat['models'] from the SDK fails to type-check against QueryModels with TS2345, because the SDK's property type shares no declared field with query's. It works at run time, so cast it: models as QueryModels.
Each call replaces the models set before, and they stay set for the process until another call, null or teardownQueryPlatform(). Query reads each property whose type or dataType is List, in any case, and takes the related model from ref, refModelName or items.model. models and properties can be objects keyed by name, or arrays with a name on each entry.
A list is looked up in the model matching the Seed's schema name, then across every model given if they agree on its key. Anything else keeps the first-word key.
Caching
Remote reads are cached in memory and, in Node, on disk under CACHE_DIR (collections/ and items/). Reads from the local source are never cached.
- Collection cache: remote
queryBySchemacalls withskip: 0and nouidPrefixorattesters, per schema and options. Each call fetches the Seed list (it shows new and revoked Seeds) and, at the same time, asks EAS whether anything a cached item depends on changed: new or revoked Versions, property edits, and the items it relates to. Only new or changed items are assembled again. maxAge: withmaxAgeorCACHE_MAX_AGEabove0, a collection this process checked less than that many seconds ago is returned without any request, so new, changed and revoked Seeds show up to that many seconds late. The time is kept in memory per schema, options andlimit, and any checked call starts it, including one made withoutmaxAge.- Item cache: remote
getSeedandgetSeeds, keyed by Seed UID, options (relationsincluded) and pin, so a pinned read and a latest read are cached apart. Checked for changes the same way on each hit. Other remote reads write their items into it. relations: 'uids'records depend only on their own Seed and Version, so an edit to a related item doesn't invalidate them.- Models: the models set are part of the key, so records cached without them aren't read with them.
- TTL: entries are rebuilt from scratch
CACHE_TTLseconds after their last full assembly, which catches changes the check can't see, like edits to a relation's relations. - ETag: the collection cache's
etagcomes from the content, so it changes with any edit. - Concurrent calls for the same schema, options and
limitshare one refresh. - Arweave bodies are cached in memory by transaction ID and link form whether or not the cache is on, up to about 32 million characters.
Cache keys change when a record's shape does, so collections and items cached by an earlier release, on disk included, aren't read, and the first reads after an upgrade assemble every record from scratch.
Pass cache: false to skip the cache for one call. To drop it from code:
import { getQueryCacheManager } from '@seedprotocol/query'
await getQueryCacheManager().clearCollection('post') // every cached listing of 'post'
await getQueryCacheManager().clearAll() // everything, including feed's cached documents in a shared CACHE_DIRFrozen records
Records from a cached read (and from a fresh skip: 0, getSeed or getSeeds read that was just stored) are the cache's own objects, shared with every later caller.
- Unless
NODE_ENVisproduction, they're deep-frozen, so assigning topost.data.titlethrows aTypeError. - In production they aren't frozen, and a write silently changes what later callers get.
- In a browser bundle
process.envusually isn't there, so records are always frozen.
Copy before you change anything, nested values included: structuredClone(post.data).
Environment variables
Query reads these when its cache manager is first created, once per process:
| Variable | Default | What it does |
|---|---|---|
CACHE_ENABLED | On, except when NODE_ENV=development | false, 0, no or off turns caching off. true, 1 or yes turns it on, including in development |
CACHE_TTL | 3600 | Seconds before an entry is rebuilt from scratch |
CACHE_DIR | ./cache | Disk cache directory, relative to the working directory (Node only). Shared with @seedprotocol/feed |
NODE_ENV | development turns caching off by default. Anything but production freezes cached records | |
CACHE_MAX_AGE | 0 | Default for queryBySchema's maxAge: seconds a checked collection is returned without a request |
And through @seedprotocol/eas and @seedprotocol/arweave, on each request:
| Variable | What it does |
|---|---|
EAS_CHAIN_ID (or NEXT_PUBLIC_EAS_CHAIN_ID) | The chain to read, and with it that chain's easscan indexer. Default OP Sepolia. The SDK's or publish's chain config takes precedence when they run in the same process |
EAS_ENDPOINT (or NEXT_PUBLIC_EAS_ENDPOINT) | An EAS GraphQL indexer URL, e.g. one you run. A URL that is a different chain's known indexer throws |
NEXT_PUBLIC_ARWEAVE_HOST, ARWEAVE_HOST, VITE_ARWEAVE_HOST | Primary Arweave gateway, first match wins. Default arweave.net |
ARWEAVE_READ_GATEWAYS (or NEXT_PUBLIC_ARWEAVE_READ_GATEWAYS) | Comma-separated fallback gateways, tried after the primary one for bodies and other Arweave reads |
Without any of these, reads go to https://optimism-sepolia.easscan.org/graphql. See EAS indexing.
Lower-level exports
Most apps only need the functions above. The package also exports the pieces they're built from:
| Area | Exports |
|---|---|
| Sources | registerLocalQuerySource, clearLocalQuerySource, getRegisteredLocalQuerySource, hasLocalQuerySource, createRemoteQueryDataSource, getRemoteQueryDataSource, resolveQuerySource, normalizeSourceMode, types QueryDataSource, SeedListFilter, ResolvedQuerySource, AttestationLike, AttestationChange. See Query adapters |
| Assembly | assembleSeeds(schemaName, seeds, options?, dataSource?): Seed attestations to SeedRecord[]. assembleSeedChangelog(seedUid, options?, dataSource?): { latestVersionUid, changelog } |
| Changelog | diffVersionSnapshots, diffPropertyAttestations, applyChangelogFilters, decodePropertyAttestation, buildFlatSnapshotFromProperties, changelogValuesEqual. decodePropertyAttestation, buildFlatSnapshotFromProperties and diffPropertyAttestations take an optional last modelName, the Seed's schema name, which picks a list's key from the models set |
| Cache | getQueryCacheManager, resetQueryCacheManager, createQueryCacheManager(config?, persistent?), CacheManager, loadQueryCacheConfig, buildAssembleOptionsKey, FileCache (Node entry), types QueryCacheConfig, QueryCacheStats, PersistentCache, CachedCollectionData, CachedItemData |
| Arweave content | hydrateArweaveRichTextInItems(items, options?), isArweaveTransactionGatewayUrl (true for /raw/<txId> on any host, and /<txId> on a known or configured gateway), resetArweaveBodyCache, enrichImageSeedClone. getArweaveUrlForTransaction(txId) is @seedprotocol/arweave's, re-exported: {first read gateway}/raw/{txId} |
| Filters | normalizeUidPrefix (see UID prefixes), normalizeAttesters (see One publisher's items) |
| Property names | parseEasPropertyMetadata, parseEasRelationPropertyName, publicListRelationPropertyKey, stripListRelationStorageAliasesForPublicKey, tryCoerceJsonStringArray |
| Rich-body markers | RICH_BODY_STORAGE_SCHEMAS, isRichBodyStorageSchema, richBodyModelPriority, getFieldStorageModels, setFieldStorageModel, getListElementStorageModels, setListElementStorageModels |
| Other types | SeedData, QuerySourceMode, AssembleOptions, GetSeedOptions, GetSeedsOptions, VersionPin, ChangelogInclude, ChangelogOptions, ChangelogEntry, VersionChangelogEntry, PropertyChangelogEntry, QueryBySchemaOptions, InitializeQueryPlatformOptions, RelationsMode, QueryModels, SeedAttestations, SeedPropertyAttestation |
DEFAULT_ARWEAVE_HOST ('arweave.net') is re-exported from @seedprotocol/arweave.
Troubleshooting
DuplicateEasModuleError or DuplicateArweaveModuleError
DuplicateEasModuleError: Two copies of @seedprotocol/eas are loaded, and EAS clients were registered on the other copy (e.g. by SeedClient). …Something, usually a running SeedClient, registered the EAS or Arweave clients through a different copy of the package than the one query imports. Reading through query's copy would use other clients, and possibly another chain, so every queryBySchema, getSeed, getSeeds, queryBySchemaForMonth and initializeQueryPlatform call throws until there's one copy. Reads with source: 'local' skip the check, because they use neither client; 'auto' reads don't.
The Seed packages pin each other to exact versions, so an install where every @seedprotocol/* package is at the same version has one copy of each. Two copies still happen when:
- A Seed package is at another version. It brings its own
easandarweave, for example@seedprotocol/queryleft a release behind the SDK. Install every@seedprotocol/*package at the same version, as the toolkit describes. - A bundler loads the same files as two modules. With Vite, see @seedprotocol/vite troubleshooting.
To check, run npm ls @seedprotocol/eas @seedprotocol/arweave: each should be at one version, with every repeat marked deduped.
TimeoutError
A remote read rejects with TimeoutError when an indexer request gets no answer within 30 seconds. It isn't retried. Check that the indexer reads go to is up; EAS indexing says which one that is. hydrateStorage's Arweave fetches have their own 15-second limit and leave the URL instead of failing.
Related
- Read published data: the walkthrough
- Query adapters: remote, local and auto sources, and custom sources
- @seedprotocol/feed: RSS, Atom and JSON Feed built on query
- @seedprotocol/eas: raw attestation reads
- Querying: reading the SDK's local database
- Permanence: what revocation does and doesn't remove
Helpers
Reference for the headless @seedprotocol/mapping API: the RSS and Markdown parsers, autoMap, applyMapping and applyMappingAsync, coerceValue, resolve callbacks, lookup and edge helpers, and saving a mapping as JSON.
Adapters
Where @seedprotocol/query reads from: the remote EAS source, the SDK's local SQLite source and auto mode, the exact fallback rules, how sources are registered, and how to write your own.