PHPackages                             decent-newsroom/expression-bundle - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. decent-newsroom/expression-bundle

ActiveSymfony-bundle

decent-newsroom/expression-bundle
=================================

Reusable Symfony bundle for Nostr expression and spell evaluation

04↑2900%PHP

Since Aug 24Pushed todayCompare

[ Source](https://github.com/decent-newsroom/expressions-bundle)[ Packagist](https://packagist.org/packages/decent-newsroom/expression-bundle)[ RSS](/packages/decent-newsroom-expression-bundle/feed)WikiDiscussions main Synced today

READMEChangelogDependenciesVersions (1)Used By (0)

ExpressionBundle
================

[](#expressionbundle)

`decent-newsroom/expression-bundle` is a reusable Symfony bundle for evaluating Nostr feed expressions and spells.

It provides the expression parser, source resolvers, pipeline runner, traversal operations, per-user result caching, and an optional authenticated HTTP endpoint. Nostr transport is implemented with [`innis/nostr-core`](https://github.com/innis/nostr-core) and [`innis/nostr-client`](https://github.com/innis/nostr-client).

Features
--------

[](#features)

- Parses addressable kind `30880` expression events.
- Evaluates filter, set, sorting, slicing, distinct, and scoring operations.
- Supports graph traversal for threaded replies, comments, and publication indexes.
- Resolves event IDs, addresses, lists, pubkey lists, expressions, and kind `777` spells.
- Uses relays directly and can optionally supplement them with a generic local event store.
- Builds a runtime context from contacts, interests, and optional user relay data.
- Caches evaluated expression and spell results per user.
- Exposes `GET /api/feed/{naddr}` for authenticated evaluations.
- Keeps persistence, relay selection, and user-relay policy behind contracts so each host can provide its own implementation.

Documentation
-------------

[](#documentation)

- [Expression runner overview](docs/expression-bundle.md)
- [Feature summary](docs/expression-bundle-feature.md)
- [Async evaluation and progress logs](docs/async-evaluation.md)
- [Expression runner OpenAPI contract](docs/expression-runner-openapi.md)
- [Spells](docs/spells.md)
- [Custom NIP drafts](docs/NIP/EX.md)

Requirements
------------

[](#requirements)

- PHP 8.3 or newer.
- Symfony 7.4 or newer.
- `innis/nostr-core` `^0.3.17`.
- `innis/nostr-client` `^0.1.7`.
- A PSR-6 cache pool and PSR-3 logger.

The package uses the `DecentNewsroom\ExpressionBundle` namespace. Its Composer mapping is package-local and does not require the consuming application's `App\` classes.

Installation
------------

[](#installation)

Install the package from its Composer repository:

```
composer require decent-newsroom/expression-bundle
```

During local development, a Symfony host can consume the package through a path repository:

```
{
    "repositories": [
        {
            "type": "path",
            "url": "packages/expression-bundle",
            "options": {
                "symlink": true
            }
        }
    ],
    "require": {
        "decent-newsroom/expression-bundle": "@dev"
    }
}
```

Register the bundle if Symfony Flex has not done so:

```
use DecentNewsroom\ExpressionBundle\ExpressionBundle;

return [
    ExpressionBundle::class => ['all' => true],
];
```

Import its routes when the HTTP endpoint is needed:

```
# config/routes/expression.yaml
expression_bundle:
    resource: '@ExpressionBundle/Resources/config/routes.yaml'
```

Configuration
-------------

[](#configuration)

The bundle uses the `expression` configuration root:

```
# config/packages/expression.yaml
expression:
    cache_ttl: 300
    max_depth: 5
    max_execution_time: 30
```

All values are optional. The defaults are 300 seconds for cached results, five levels of nested expression traversal, and 30 seconds of evaluation time.

Host integration
----------------

[](#host-integration)

The bundle is deliberately storage- and policy-agnostic. A consuming application must provide the relay contracts and may optionally provide a generic local event store:

ContractResponsibility`EventInterface`Scalar access to a Nostr event.`EventStoreInterface`Optional generic local event lookup, filtering, and reference queries.`RelaySelectorInterface`Default, content, author, and local-relay selection plus URL normalization.`RelayEventClientInterface`Relay event fetching. The package includes `InnisRelayEventClient`.`UserRelayProviderInterface`Optional per-user relay resolution for runtime context and relay probes.The host should alias each required interface to its adapter:

```
services:
    DecentNewsroom\ExpressionBundle\Contract\EventStoreInterface:
        alias: App\Integration\ExpressionEventStore

    DecentNewsroom\ExpressionBundle\Contract\RelaySelectorInterface:
        alias: App\Integration\ExpressionRelaySelector

    DecentNewsroom\ExpressionBundle\Contract\RelayEventClientInterface:
        alias: DecentNewsroom\ExpressionBundle\Infrastructure\InnisRelayEventClient

    DecentNewsroom\ExpressionBundle\Contract\UserRelayProviderInterface:
        alias: App\Integration\ExpressionUserRelayProvider
```

`EventStoreInterface` and `UserRelayProviderInterface` are optional. Without a local event store, all event, list, filter, and traversal lookups use relays. Without a user relay provider, evaluations still work using the host's default relay selection.

### Nostr client and bech32 services

[](#nostr-client-and-bech32-services)

`InnisRelayEventClient` uses the Innis client port (`Innis\Nostr\Client\Application\Port\NostrClientInterface`). The recommended way to supply it is `decent-newsroom/nostr-client-bundle`(`DecentNewsroom\NostrClientBundle`), which wires the same `innis/nostr-client` factory with configurable connection/reconnect defaults (see `../nostr-client-bundle/docs/nostr-client-bundle.md` in this packages directory). If that bundle is not installed, the host can wire the port manually instead:

```
services:
    Innis\Nostr\Client\Application\Port\NostrClientInterface:
        factory: ['Innis\Nostr\Client\Infrastructure\Factory\NostrClientFactory', 'create']
        arguments:
            - '@logger'

    Innis\Nostr\Core\Domain\Service\Bech32EncoderInterface:
        class: Innis\Nostr\Core\Infrastructure\Adapter\Bech32EncoderAdapter
```

Relay communication is read-only in this bundle. The built-in client connects to the relay URLs selected by `RelaySelectorInterface`, builds Innis filters, subscribes, and converts received events to the package `EventInterface`representation.

Caching
-------

[](#caching)

`ExpressionService` exposes cached and uncached evaluation methods:

- `evaluate()` and `evaluateSpell()`
- `evaluateCached()` and `evaluateSpellCached()`
- `getCachedResults()` and `getCachedSpellResults()`

The package owns cache-key generation and result caching. By default, `FeedCacheService` uses Symfony's `cache.app` pool. Hosts can provide a dedicated pool:

```
# config/packages/cache.yaml
framework:
    cache:
        pools:
            expression.cache:
                adapter: cache.adapter.redis
                provider: Redis
                default_lifetime: 300
```

```
# config/services.yaml
services:
    DecentNewsroom\ExpressionBundle\Service\FeedCacheService:
        arguments:
            $cache: '@expression.cache'
            $expressionCacheTtl: '%expression.cache_ttl%'
```

Cache keys include the expression coordinate and publication timestamp, the user pubkey, contacts, and interests. Republishing an expression therefore does not reuse the previous result cache.

HTTP API
--------

[](#http-api)

The optional authenticated endpoint is:

```
GET /api/feed/{naddr}?offset=0&limit=50

```

It requires `ROLE_USER`, decodes the Nostr address, evaluates the expression for the authenticated user, and returns:

```
{
    "expression": "30880:pubkey:identifier",
    "count": 1,
    "offset": 0,
    "limit": 50,
    "events": []
}
```

The response limit is capped at 500 events.

Evaluation flow
---------------

[](#evaluation-flow)

1. `ExpressionService` creates a user runtime context.
2. The parser converts the expression or spell into a pipeline.
3. `EventResolver` queries the optional generic local store and relays.
4. The runner applies operations and traversal rules.
5. The final `NormalizedItem[]` result is returned or cached.

The runtime context can include:

- kind `3` contacts;
- kind `10015` interests;
- optional kind `10002` user read relays.

Testing
-------

[](#testing)

Install the package dependencies and run its package-owned test suite:

```
composer install
vendor/bin/phpunit -c phpunit.xml.dist
```

The package tests use `ArrayEvent` and contract mocks, so they do not require a Doctrine entity or a newsroom database.

Scope and limitations
---------------------

[](#scope-and-limitations)

This package does not provide:

- a database implementation;
- a global relay registry or user relay-list policy;
- relay publishing, signing, or NIP-42 authentication;
- application authentication or user management.

Those concerns belong to the consuming Symfony host and are connected through the package contracts and service configuration.

###  Health Score

22

—

LowBetter than 21% of packages

Maintenance65

Regular maintenance activity

Popularity5

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity11

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/18485210?v=4)[Nusa](/maintainers/nusapuksic)[@nusapuksic](https://github.com/nusapuksic)

---

Top Contributors

[![nusapuksic](https://avatars.githubusercontent.com/u/18485210?v=4)](https://github.com/nusapuksic "nusapuksic (1 commits)")

### Embed Badge

![Health badge](/badges/decent-newsroom-expression-bundle/health.svg)

```
[![Health](https://phpackages.com/badges/decent-newsroom-expression-bundle/health.svg)](https://phpackages.com/packages/decent-newsroom-expression-bundle)
```

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
