Files
Sylvain Duchesne fd6d408de1 deps: install @ng-org packages from npm instead of local tarballs
The @ng-org/* SDK alpha versions are now published on npm. Switch the
package.json dependencies from .ng-tarballs/*.tgz paths to npm
versions, removing the need for a sibling nextgraph-rs build to
install the project. Makes containerized deploys (Coolify) trivial.

- @ng-org/alien-deepsignals: 0.1.2-alpha.11 (unchanged)
- @ng-org/orm: 0.1.2-alpha.15 → 0.1.2-alpha.18
- @ng-org/shex-orm: 0.1.2-alpha.7 → 0.1.2-alpha.8
- @ng-org/web: 0.1.2-alpha.11 → 0.1.2-alpha.13

scripts/build-ng-packages.sh and the .ng-tarballs/ workflow remain
available for local development against an unreleased nextgraph-rs
build (`.gitignore` keeps the directory ignored).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 12:03:36 +02:00

5.3 KiB

Data Layer

NextGraph-backed local-first data with fallback to local state for demo/disconnected mode.

Overview

The app has two data modes:

  1. Connected — NextGraph ORM shapes (P2P, encrypted, local-first)
  2. Disconnected/Demo — Local React state seeded from seedData.ts

All screens use useFestipodData() hook regardless of mode.

NextGraph Stack

@ng-org/web          # Browser WASM runtime
@ng-org/orm          # RDF shape-based ORM
@ng-org/shex-orm     # SHEX → TypeScript code generation
@ng-org/alien-deepsignals  # Reactive signals bridge

Packages installed from npm (@ng-org/* alpha versions). For local development against an unreleased nextgraph-rs build, scripts/build-ng-packages.sh packs the monorepo into .ng-tarballs/ and updates package.json to point at those paths.

SHEX Shapes

src/shared/shapes/shex/festipodShapes.shex defines:

  • Event — title, description, dates, location, themes, participants
  • UserProfile — name, username, bio, city, visibility
  • Participation — links event + user, confirmation status

ORM bindings in src/shared/shapes/orm/:

  • festipodShapes.schema.ts — Schema registration
  • festipodShapes.shapeTypes.ts — Shape type constants
  • festipodShapes.typings.ts — TypeScript interfaces

Regenerate with bun run build:orm.

NextGraph Read/Write Pattern

The app follows the same pattern as the official expense-tracker-rdf example:

  • Scope: useShape(shapeType, did🆖${session.private_store_id}) — opens the private store repo in the verifier
  • @graph: did:ng:${session.private_store_id} — writes target the same NURI

This is critical: orm_start_graph with the private store NURI explicitly opens the repo in the verifier's self.repos HashMap. Without this, orm_frontend_update fails with RepoNotFound.

Do NOT use did:ng:i as scope — it subscribes to the entire user site via a special code path that doesn't open individual repos, breaking all writes.

Deleting Objects

ngSet.delete(item) updates the local reactive set but does not persist to the broker. Use ng.sparql_update() with SPARQL DELETE instead:

import { ng } from '@ng-org/web';
import { sessionPromise } from '../utils/ngSession';

const session = await sessionPromise;
await ng.sparql_update(
  session.session_id,
  `DELETE WHERE { GRAPH <${item["@graph"]}> { <${item["@id"]}> ?p ?o } }`,
  item["@graph"],
);

The broker sends back a GraphOrmUpdate with op: "remove" that reactively removes the item from the ORM set. Do NOT combine with ngSet.delete() — the two operations conflict in the CRDT.

See decision record for details.

Key files

  • src/shared/hooks/useShapeWithDefaults.ts — Accepts storeNuri param, passes to useShape
  • src/shared/utils/ngGraph.tsensureGraphNuri() returns @graph for entity creation
  • src/shared/utils/ngBootstrap.ts — Seeds test data using ensureGraphNuri() for @graph

See decision record for why.

Context Providers

NextGraphContext (src/shared/context/NextGraphContext.tsx)

  • Connection lifecycle: disconnectedconnectingconnected | error
  • Provides session with store IDs (private, protected, public)
  • Conditional auto-init: Only auto-calls initNg() when running inside the broker iframe (window.self !== window.top). Outside the iframe, initNgWeb() would redirect the page to the broker — so connection waits for explicit connect() call.
  • connect(): Called by user clicking "Se connecter". When outside broker, triggers the redirect flow.

@ng-org/web redirect behavior

initNgWeb() checks window.self === window.top. If the app is NOT in an iframe, it redirects to nextgraph.net/redir/ with the current URL encoded as a return parameter. The broker then loads the app back in an iframe after auth. This means the app must NOT auto-init NG when loaded standalone.

FestipodDataContext (src/shared/context/FestipodDataContext.tsx)

  • Wraps NextGraph shapes with useShapeWithDefaults() hook
  • CRUD: createEvent(), updateEvent(), joinEvent(), leaveEvent(), etc.
  • Exposes useFestipodData() hook consumed by all screens
  • selectedEventId state for cross-screen event navigation
  • loadTestData(): Calls bootstrapWallet() to seed test data into NG wallet — only triggered by explicit user action
  • Provider states based on NG status:
    • disconnectedLocalDataProvider with seed data (demo mode)
    • connectingLocalDataProvider with empty data (avoids flashing seed data before wallet loads)
    • connectedNgDataProvider with real wallet data
    • errorLocalDataProvider with seed data (graceful fallback)

Data Types

src/shared/data/types.ts:

  • FpEventData — id, title, date, location, distance, themes, etc.
  • FpUserData — id, name, username, bio, city, counts
  • FpParticipationData — eventId + userId + confirmed
  • FpMeetingPointData — eventId, location, time, host (local-only)
  • FpFriendshipData — userId + friendId (local-only)

Seed Data

src/shared/data/seedData.ts:

  • 10 users (Marie Dupont = current user, user-1)
  • Multiple events with dates, locations, themes
  • Participations, meeting points, friendships
  • CURRENT_USER_ID = 'user-1'