npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@aimform/spaces

v0.3.5

Published

Aimform Spaces — general-purpose content model: Spaces, Entities, Blocks, Properties, Views, Files, Connections, and Conversations.

Readme

@aimform/spaces

spaceType is an extensible string projection. The built-in semantic values are project, ability-app, ability-skill, ability-tool, and ability-package. Ability source Spaces are linked by the API's ability_sources projection and remain ordinary Spaces for Files, Entities, and Git-backed source operations.

General-purpose content model — Spaces, Entities, Blocks, Properties, Collections, Views, Files, Connections, and Conversations.

Install

npm install @aimform/spaces @aimform/network-client @aimform/identity

Usage

import { Database, D1Driver } from "@aimform/db";
import { Spaces, Entities, Blocks, Collections, Properties, Views, Files, Connections, Conversations, spacesSchema }
  from "@aimform/spaces";

// Service-side relational persistence
const database = new Database({ schema: spacesSchema, driver: new D1Driver(env.DB) });
const spaces = new Spaces({ database });
const blocks = new Blocks({ database });

// Platform API
const aimform = createAimform({ apiKey: "af_xxx" });
const spaces = new Spaces({ client: aimform.client });
const entities = new Entities({ client: aimform.client });

For service-side persistence, construct one authorized Database at the composition root and pass it through the package APIs:

import { Database, D1Driver } from "@aimform/db";
import { DatabaseKnowledgeCollectionAdapter, spacesSchema } from "@aimform/spaces";

const database = new Database({ schema: spacesSchema, driver: new D1Driver(env.DB) });
const knowledge = new DatabaseKnowledgeCollectionAdapter(database);

Request contexts may expose that same instance as both ctx.db and ctx.database. Raw provider bindings are not application database surfaces.

The conversations model includes optional thread metadata fields (threadKind, surface, parentConversationId, rootConversationId, and conversationVersion). Existing rows remain valid; services may populate these fields during the initial conversation insert to avoid a second update.

The server-side DatabaseFilesAdapter accepts an injected StorageAdapter for direct file operations. It writes uploaded bytes to object storage before persisting metadata, reads content through the stored object key, and deletes the object before removing its metadata row.

Knowledge collection reads are non-mutating. DatabaseKnowledgeCollectionAdapter.listMany(scope, collectionIds) reads several knowledge collections from one scoped entity query and preserves the same scope filtering as list.

Spaces

import { Spaces } from "@aimform/spaces";

const spaces = new Spaces({ database: db });
await spaces.list("org_456");
await spaces.listChildren("org_456"); // root-level Spaces and folders
await spaces.listChildren("org_456", "spc_123"); // direct children, like ls
await spaces.listChildren("org_456", "spc_123", "folder"); // folders only
await spaces.get("spc_123");
await spaces.create({ ownerType: "organization", ownerId: "o1", name: "Project", createdByProfileId: "p1" });
await spaces.update("spc_123", { name: "Renamed" });
await spaces.delete("spc_123");
await spaces.getIndex("spc_123");

Spaces can be nested by setting parentSpaceId; use spaceType: "folder" for a folder Space. File paths are stored as relative directory metadata, and Git-backed repository snapshots preserve file identity with LFS pointers while edits update the existing file object.

→ Spaces docs

Entities

import { Entities } from "@aimform/spaces";

const entities = new Entities({ database: db });
await entities.list("spc_123", { entityType: "task" });
await entities.count("spc_123", { entityType: "task" }); // cheap COUNT(*) — prefer over list() for badges/overview counts
await entities.get("ent_789");
await entities.create({ spaceId: "spc_123", entityType: "task", properties: { title: "Setup CI" }, createdByProfileId: "p1" });
await entities.update("ent_789", { properties: { status: "done" } });
await entities.delete("ent_789");

→ Entities docs

Public content for SSR

Collections can be explicitly shared for public, read-only rendering using CollectionShares (authenticated create/revoke) and PublicContent (anonymous SSR reads). The public endpoint returns only live Collection Entities marked with properties.status: "published" or properties.published: true, together with their ordered Blocks. Create a Collection share link through the authenticated Space route, keep the returned token in server-side config, and load it with the SDK:

import { AimformClient } from "@aimform/network-client";
import { CollectionShares, PublicContent } from "@aimform/spaces";

const editorClient = new AimformClient({ apiKey: process.env.AIMFORM_API_KEY! });
const share = await new CollectionShares(editorClient).create(orgId, spaceId, "blog_post");
// Store share.token in server-side config; it grants public read-only access.
const publicContent = new PublicContent(new AimformClient({ baseUrl: "https://api.aimform.com" }));
const { collection, items } = await publicContent.getCollection(share.token);

The token grants read-only access to published items and is revocable. It does not grant access to drafts or write operations.

Blocks

import { Blocks } from "@aimform/spaces";

const blocks = new Blocks({ database: db });
await blocks.list("ent_789");
await blocks.add("ent_789", { blockType: "paragraph", content: "Hello world" });
await blocks.addSource(block.id, { sourceType: "url", sourceUrl: "https://...", excerpt: "...", confidence: "high" });
await blocks.update(block.id, { content: "Updated" });
await blocks.delete(block.id);
await blocks.removeSource(block.id, source.id);

→ Blocks docs

Collections

import { Collections } from "@aimform/spaces";

const collections = new Collections({ database: db });
await collections.list("spc_123");
await collections.upsert({
  spaceId: "spc_123", entityType: "task",
  renderMode: "record", presentation: "collection",
  displayName: "Tasks", pinned: true, position: 0,
});
await collections.delete("spc_123", "task");

→ Collections docs

Properties

import { Properties } from "@aimform/spaces";

const properties = new Properties({ database: db });
await properties.list("spc_123", "task");
await properties.create({ spaceId: "spc_123", key: "priority", label: "Priority", valueType: "select" });
await properties.createMany([
  { spaceId: "spc_123", entityType: "task", key: "status", label: "Status", valueType: "select" },
  { spaceId: "spc_123", entityType: "task", key: "dueAt", label: "Due at", valueType: "date" },
]);
await properties.update("prop_1", { label: "Task Priority" });
await properties.delete("prop_1");

→ Properties docs

Views

import { Views } from "@aimform/spaces";

const views = new Views({ database: db });
await views.list("spc_123");
await views.create({ spaceId: "spc_123", name: "Active Tasks", viewType: "table", entityType: "task" });
await views.update("view_1", { filters: JSON.stringify([{ field: "status", operator: "eq", value: "active" }]) });
await views.delete("view_1");

→ Views docs

Files

import { Files } from "@aimform/spaces";

const files = new Files({ database: db });
await files.list("org_456");
await files.get("file_1");
await files.put({ orgId: "org_456", r2Key: "reports/q4.pdf", mimeType: "application/pdf", sizeBytes: 1024 });
await files.delete("file_1");
await files.getPresignedUrl("reports/q4.pdf", "application/pdf", 3600);

→ Files docs

Automations

import { Automations } from "@aimform/spaces";

const automations = new Automations({ client: aimform.client });
const lineage = await automations.getDecisionLineage("org_456", "run_123");
console.log(lineage.coverage, lineage.items);

getDecisionLineage is a read-only, organization-authorized audit view over one durable Workflow run. It preserves references across events, Function invocations, AI and policy decisions, human approvals, external effects, and the final result.

Connections

import { Connections } from "@aimform/spaces";

const connections = new Connections({ database: db });
await connections.list("org_456");
await connections.create({ orgId: "org_456", provider: "xero", credentialHandle: "vault://object/xero/connection-1" });
await connections.listProviders();
const result = await connections.resolveCapability("org_456", "financial:transactions:read");
await connections.getCanonicalData("conn_1", "financial:transactions:read", { limit: 50 });
await connections.grantAccess("conn_1", "client_1", ["financial:transactions:read"]);
await connections.revoke("conn_1");

→ Connections docs

The Apps connection surface accepts credentialValue only while establishing a connection. The API stores that value in Vault and returns only credential_handle plus credential_status; callers must use explicit capability grants when assigning a connection to a Function or Workflow.

App onboarding is manifest-driven. Apps.getProviderSetup() exposes the managed Aimform default, its billing label, optional BYOD credential fields, and provider manual steps. Apps.validateProviderCredentials() performs the pre-persistence Form Intake check. Use connectionMode: "byod" only when the organisation owns the provider credentials; the default managed mode is billed through Aimform. GoCardless onboarding may provide a validated returnPath to return to an App's native in-app surface after authorization.

Apps.listChatAppState() returns the provider catalog and connection statuses in one response for server-rendered chat connection cards.

Connection Functions are reusable and always scoped to one Connection:

const functions = await apps.listConnectionFunctions("org_456", "conn_bank_1");
const accounts = await apps.invokeConnectionFunction("org_456", "conn_bank_1", "getAccounts");
const balances = await apps.invokeConnectionFunction("org_456", "conn_bank_1", "getBalances", {
  accountId: "account_123",
});
const transactions = await apps.invokeConnectionFunction("org_456", "conn_bank_1", "listTransactions", {
  accountId: "account_123",
});

Each bank authorization is an independent Connection. Provider account IDs are verified and stored as safe Connection metadata, while provider tokens remain behind Vault; account reads cannot cross into another bank Connection.

Conversations

import { Conversations } from "@aimform/spaces";

const conversations = new Conversations({ database: db });
await conversations.list("org_456", "prof_123");
await conversations.create({ orgId: "org_456", profileId: "prof_123", contextScope: "space:spc_123" });
await conversations.appendMessage("conv_1", { role: "user", content: "Hello" });
const conv = await conversations.get("conv_1");
await conversations.delete("conv_1");

→ Conversations docs

→ Creating custom adapters

Gmail provider

Gmail connection reads, writes, attachment downloads, and normalized sync records are owned by Spaces. The deployed Sync service composes this provider with Trust-owned Google OAuth credentials.

import { GmailAdapter } from "@aimform/spaces";

const gmail = new GmailAdapter(accessToken);
const messages = await gmail.listMessages("newer_than:14d");

Production Gmail composition passes { guard } as the adapter options. Guard admission happens before Gmail requests and tracks request/fanout usage without coalescing semantically distinct mailbox events.