
Security News
Happy Birthday, Shai-Hulud
It has been one year since Shai-Hulud made its first appearance on npm.
@ariestools/aries-dapp-core
Advanced tools
Aries dapp backend core: the DappActor (forked xl1 actor pattern), process-wide provider locator, pluggable reducer, and the composed daemon that runs the storage server plus actors
Dapp backend core: a process-wide provider locator, pluggable reducer, coherent
generation publication for S3/R2-backed indexes, and the stock DappActor
timer. Designed for outbound-only production indexers (no public inbound
API on the compute process) as well as the local aries dapp development
fixture.
| Surface | Purpose |
|---|---|
aries-dapp-server bin + noopReducer | Local development only — embedded s3rver via @ariestools/aries-dapp-serve, memory backing, no real derivation |
| Project-owned entrypoint + real reducer | Production — inject an S3/R2 client, bind logical roles to buckets/prefixes, publish immutable generations |
dapp-serve and the noop daemon remain explicit local tools. Production usage
is a project-owned executable that composes this package; dapp-core does
not dynamically load arbitrary reducer modules and does not store application
authority (signing seeds, Webble controller roots, etc.).
Published state and indexes are rebuildable projections, not authority. Public buckets must not be assumed safe for plaintext private material.
AbstractActor — create/start/stop lifecycle, non-overlapping timers
(firstRunDelay is the real delay before the first pass), abort signal on
stop, optional shutdown bound for in-flight work, readiness contract.DappProviderLocator / createDappLocator — process-wide locator. One
shared S3 client across roles, or per-role clients for least-privilege
principals; logical role → physical bucket + prefix + optional public CDN base.
The data role is registered read-only by default (put throws).DappObjectReader / DappObjectWriter / DappBucketStore — capability
split: reducers see data as read-only; state/index remain writers.
get/getWithMeta/stat/put (metadata + conditional writes)/list*/destroy.publishGeneration / readPublishedHead — fenced publication: create-only
generation objects, head-last with conditional-head CAS or injected lease
(or unfenced for local s3rver only). Returns { ok: true, … } or
{ ok: false, conflict }.writeIndexerStatus / readIndexerStatus — durable floor/cursor,
completed position, observed source head, generation, success/error
timestamps, consecutive failures (CDN-revalidate).DappActor — drives a DappReducer on an interval; optional status
publication; optional maxConsecutiveFailures for supervisor restarts.bootDappActors — starts N actors, runs readiness, stops earlier actors
if a later one fails.import { S3Client } from '@aws-sdk/client-s3'
import {
bootDappActors,
createDappLocator,
publishGeneration,
type DappReducer,
} from '@ariestools/aries-dapp-core'
const client = new S3Client({
region: 'auto',
endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
forcePathStyle: true,
credentials: {
accessKeyId: process.env.R2_ACCESS_KEY_ID!,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
},
})
const locator = createDappLocator({
client,
ownClient: true,
bindings: {
data: {
bucket: 'my-dapp',
prefix: 'data',
publicBaseUrl: 'https://cdn.example.com/data',
},
state: {
bucket: 'my-dapp',
prefix: 'state',
publicBaseUrl: 'https://cdn.example.com/state',
},
index: {
bucket: 'my-dapp',
prefix: 'index',
publicBaseUrl: 'https://cdn.example.com/index',
},
},
})
const reducer: DappReducer = {
name: 'my-app-indexer',
version: '1',
async reduce({ state, index, signal, logger }) {
const published = await publishGeneration({
state,
index,
safety: { mode: 'conditional-head' }, // fail closed if provider lacks CAS
// expectedHeadEtag: prior?.headEtag,
reducer: { name: 'my-app-indexer', version: '1' },
source: { cursor: '…', completedPosition: '…', observedHead: '…' },
stateObjects: [{ key: 'view.json', body: '{}', contentType: 'application/json' }],
indexObjects: [{ key: 'by-id.json', body: '{}', contentType: 'application/json' }],
signal,
})
if (!published.ok) {
logger.warn(`publish conflict: ${published.conflict.kind} ${published.conflict.message}`)
return // do not advance checkpoint on conflict
}
logger.info(`published ${published.generation}`)
return {
cursor: '…',
lastCompletedPosition: '…',
observedSourceHead: '…',
generation: published.generation,
generationRoot: published.manifestKey,
}
},
}
const actors = await bootDappActors(locator, [{
name: 'MyIndexer',
reducer,
firstRunDelayMs: 1_000,
reduceIntervalMs: 15_000,
maxConsecutiveFailures: 10,
publishStatus: true,
}])
Clients never contact this process. They read head.json, generation objects,
and status.json from the public CDN/base URLs. Credentials stay out of
published config and logs.
See also src/examples/productionComposition.ts.
state/head.json # mutable head (revalidate CDN)
state/status.json # indexer status (revalidate CDN)
state/generations/<id>/manifest.json # immutable
state/generations/<id>/… # immutable state objects
index/generations/<id>/… # immutable index objects
Head is written last. A crash before head leaves the previous complete generation visible. Checkpoints must not advance past a fully published generation (the actor only records generation after a successful reduce that published head).
The composed bin (dist/bin/dappServer.mjs) starts the storage server and a
DappActor. aries dapp up spawns it.
# Empty spine (noop reducer)
aries dapp up
# Real project reducer (ESM .mjs or package export — TypeScript not supported)
aries dapp up --reducer ./dist/indexer.mjs
aries dapp up --reducer ./dist/indexer.mjs --reducer-export myIndex
aries dapp up --reducer @myorg/my-dapp/indexer
# Persist buckets across restarts
aries dapp up --backing disk --data-dir ~/.aries/dapp/data
Example reducer shipped with this package:
aries dapp up --reducer ./node_modules/@ariestools/aries-dapp-core/examples/countFactsReducer.mjs
# monorepo:
aries dapp up --reducer ./packages/dapp-core/examples/countFactsReducer.mjs
The module must export a DappReducer:
reducer, or--reducer-export / DAPP_REDUCER_EXPORT// dist/indexer.mjs
export default {
name: 'my-index',
version: '1',
async reduce({ data, state, index, signal, logger }) {
// read data, publishGeneration / publishIncremental, return progress
},
}
Local publication should use safety: { mode: 'unfenced' } (or honor
DAPP_PUBLICATION_SAFETY, which the daemon sets to unfenced by default).
s3rver does not support reliable head CAS.
| Variable | Default | Notes |
|---|---|---|
DAPP_PORT | 8801 | |
HOST | 127.0.0.1 | |
DAPP_BACKING | memory | memory | disk |
DAPP_DATA_DIR | ~/.aries/dapp/data | disk root |
DAPP_REDUCER | (noop) | path to .mjs/.js or package export |
DAPP_REDUCER_EXPORT | default / reducer | named export |
DAPP_REDUCE_INTERVAL_MS | 5000 | |
DAPP_FIRST_RUN_DELAY_MS | 0 | |
DAPP_PUBLICATION_SAFETY | unfenced | set by daemon for project reducers |
DAPP_PUBLIC_HOST / DAPP_TLS_* | --ssl auto |
Under --ssl auto the daemon exports the local CA via NODE_EXTRA_CA_CERTS.
Do not run aries-dapp-server or aries dapp up in production. Write a
project-owned process that:
S3Client for R2/S3 (credentials from env/IAM — never logged)createDappLocator({ client, bindings, ownClient: true })DappReducer (import statically — no dynamic CLI load required)bootDappActors with safety: { mode: 'conditional-head' } inside
publishGeneration / publishIncrementallocator.destroy()See examples/productionComposition.ts and the composition section above.
There is no inbound public listener; clients read head/status/objects from CDN.
s3:GetObject, s3:PutObject, and
usually s3:ListBucket on the role prefix. Data role: GetObject/ListBucket
only (no PutObject). Prefer distinct access keys via createDappLocator({ clients: { data, state, index } }).GET/HEAD only. Deny s3:ListBucket and
all Put*/Delete* for the public principal. dapp-core never requires public
LIST or PUT.| Mode | Use |
|---|---|
conditional-head | Production when the provider supports If-Match / If-None-Match |
lease | Production with an application-injected lease/fencing adapter |
unfenced | Local s3rver / tests only — no concurrent-writer safety |
import {
listReleases, pinRelease, rollbackHead,
planGarbageCollection, runGarbageCollection,
} from '@ariestools/aries-dapp-core'
await pinRelease(state, 'gen-a', { reason: 'hotfix' })
await rollbackHead(state, {
targetGeneration: 'gen-a',
safety: { mode: 'conditional-head' },
expectedHeadEtag: currentEtag,
})
const plan = await planGarbageCollection({
state,
index,
policy: { mode: 'bounded', retainGenerations: 5, cdnGraceMs: 86_400_000 },
})
await runGarbageCollection(plan, { state, index }, { dryRun: true })
Source data is never GC'd. Active head, pins, and in-flight generations are protected.
For large sparsely-changing indexes use publishIncremental (CAS bodies + 256-way
sharded map). Unchanged keys keep stable cas/sha256/<hash> URLs; each pass
writes only changed objects + touched shards + root + receipt.
import { publishIncremental } from '@ariestools/aries-dapp-core'
const result = await publishIncremental({
state,
index,
safety: { mode: 'conditional-head' },
expectedHeadEtag: prior?.headEtag,
reducer: { name: 'addr-index', version: '1' },
indexChanges: [{ key: 'by-source/0xabc', body: frameJson, contentType: 'application/json' }],
})
import {
verifyPublishedView, resolveObject,
} from '@ariestools/aries-dapp-core/consumer'
const view = await verifyPublishedView({
fetch,
bases: { state: 'https://cdn.example/state', index: 'https://cdn.example/index' },
})
const one = await resolveObject({
fetch,
bases: { state: 'https://cdn.example/state', index: 'https://cdn.example/index' },
logicalKey: 'by-source/0xabc',
role: 'index',
})
No Node built-ins or AWS SDK on this subpath.
Ordinary CI does not require R2 secrets. With credentials:
export DAPP_R2_CONTRACT_TEST=1
export R2_ACCOUNT_ID=…
export R2_ACCESS_KEY_ID=…
export R2_SECRET_ACCESS_KEY=…
export R2_BUCKET=…
# optional: R2_PUBLIC_BASE_URL=https://pub-….r2.dev
pnpm xy test @ariestools/aries-dapp-core
See src/spec/r2.contract.spec.ts.
FAQs
Aries dapp backend core: the DappActor (forked xl1 actor pattern), process-wide provider locator, pluggable reducer, and the composed daemon that runs the storage server plus actors
We found that @ariestools/aries-dapp-core demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 3 open source maintainers collaborating on the project.

Security News
It has been one year since Shai-Hulud made its first appearance on npm.

Research
/Security News
Operators behind PolinRider used a compromised GitHub account to plant malware in four development versions of a Packagist package with 700,000+ downloads.

Security News
GitHub Actions now supports cache-mode, a least-privilege control on the Actions cache aimed at the cache poisoning technique behind recent compromises.