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

@colixsystems/action-sdk

v0.9.0

Published

Authoring contract for AppStudio Actions — marketplace deliverables that ship server-side action code. Implements ActionManifest, the action contract, and propertySchema validation.

Downloads

1,334

Readme

@colixsystems/action-sdk

The authoring contract for an AppStudio Action.

An Action is a marketplace deliverable that ships server-side action code only. It has no React component and renders nothing, so it carries none of the widget concerns — no supportedPlatforms, no events, no inputs, no styleSchema — and it has no web↔native parity surface: an action runs in the backend's isolated-vm runner regardless of how the app is rendered.

A widget renders; an action automates. If you are building a component the app displays, you want @colixsystems/widget-sdk instead. The two are separate deliverables with separate developer guides, separate submit flows and separate review — but they live in the same marketplace and can be bundled together.

Install

npm install @colixsystems/action-sdk

The manifest

export default {
  id: "com.acme.fortnox.sync",
  name: "Fortnox Sync",
  version: "1.0.0",
  category: "ACCOUNTING",
  icon: "lucide:plug",
  description: "Pushes new invoices to Fortnox every 15 minutes.",
  author: { name: "Acme AB", url: "https://acme.example" },
  minAppStudioVersion: ">=0.1.0",
  requestedScopes: ["datastore:read", "datastore:write"],

  // Filled in by the INSTALLING OPERATOR on the Studio's Integrations page —
  // not by a page author in the builder. The resolved values are delivered to
  // every action below.
  propertySchema: {
    baseUrl: { type: "string", label: "API base URL", required: true },
    apiToken: { type: "secret", label: "API token", required: true },
    batchSize: { type: "number", label: "Batch size", default: 50 },
  },

  actions: [
    {
      key: "push-invoices",
      name: "Push new invoices",
      description: "Sends invoices created since the last run.",
      triggerTypes: ["schedule"],
      scheduleCron: "*/15 * * * *",
      timeoutMs: 30000,
      // The code lives in its own file; `pack` inlines it.
      script: "scripts/push-invoices.js",
    },
  ],
};
my-action/
├── manifest.js
├── package.json
└── scripts/
    └── push-invoices.js   ← ordinary JavaScript, linted by your editor

What's new in 0.8.0 (contract 0.3.0)

An action's code can live in its own .js file. Declare script: "scripts/<name>.js" instead of an inline scriptSource string, and appstudio-action lint / pack read the file and inline it for you.

  • Why. A script embedded in the manifest as a template literal loses every tool that reads .js: syntax highlighting, your editor's linter, jump-to-definition, a readable diff. A manifest carrying several actions became mostly code with the metadata buried in it.
  • The path is relative to the manifest and may not escape the package — the tarball is what gets published, so a traversing reference would either miss or pull in a file you never meant to ship.
  • It is an authoring convenience, resolved before anything leaves your machine. pack writes appstudio.action.json with scriptSource inlined verbatim, so the artefact the marketplace stores, reviews, pins a manifest_sha256 over and runs is unchanged. Nothing about publishing, review or the runner is different.
  • Declare one or the other, never both — that would leave real doubt about which code ships, so it is refused with the action's key in the message. Inline scriptSource remains fully supported for a one-liner, and every already-published Action is unaffected.
  • A script that reaches the wire is refused (manifest.actions[].script is a source reference, not publishable). That means the artefact was hand-built or pack was skipped, so the code never made it — better a clear rejection than an Action that installs and no-ops.

Fields

| Field | Required | Notes | | ----- | -------- | ----- | | id | yes | Reverse-DNS, e.g. com.acme.fortnox.sync. | | name | yes | Human-readable. | | version | yes | Semver. | | category | yes | One of the storefront categories (see CONTRACT.manifestCategories). | | icon | yes | Icon identifier, e.g. lucide:plug. | | description | yes | 1–2 sentence storefront description. | | author | yes | { name, url?, email? }. | | minAppStudioVersion | yes | Semver range. | | requestedScopes | yes | [] unless the actions read/write data. | | propertySchema | yes | Operator-supplied configuration. {} if none. | | actions | yes | At least one, at most 16. The action's whole payload. | | datastoreTemplate | no | Tables seeded at install (8 tables, 24 columns each). | | translations | no | Strings merged into the tenant dictionary under action.<id>.<key>. |

Property types

string, number, boolean, secret, select, multiselect, tableRef, columnRef, object, array.

A secret property is stored encrypted at rest, is never returned by any read path, and is redacted out of captured action output. It may not declare a default — a credential does not belong in a manifest.

The page-authoring types a widget has (pageRef, eventBinding, expression, recordBinding, valueRef, asset, icon, image, richText, …) address a page or a rendered component, so they are not available here.

Action scripts

An action script is not a React component. It runs against the runner globals and has no SDK imports, no hooks, no React and no DOM:

datastore, secrets, properties, fetch, connectors, notifications, console, record, request, tenantId, triggerType, triggerTableId

triggerTypes may hold any of schedule, record_created, record_updated, record_deleted, manual. It may not hold app or http_post (sc-5366): those expose the script to a published app's buttons or to a public webhook, so the installing workspace grants them in its Actions admin page — a manifest that declares one is rejected. request is { body } — the JSON an inbound http_post caller sent — and null on every other trigger; request headers are never passed through.

properties is this install's configuration — the values the operator filled in for your propertySchema plus the hiddenProperties you shipped, merged into one frozen object. Read properties.baseUrl, not a hard-coded literal.

Limits: scriptSource ≤ 200 KiB, timeoutMs between 100 and 300000.

Never declare triggerTableId or apiKeyId in the manifest — those are tenant-local and the operator binds them after install.

The CLI

appstudio-action lint <dir|manifest>   # validate the manifest + every action script
appstudio-action pack <dir>            # emit appstudio.action.json, then npm pack

lint runs two passes: the manifest's structure (the same validateManifest the publish endpoint runs, so the two can never disagree) and each action's scriptSource against the runner surface. It reports every finding and exits non-zero only on an error — a warning is printed but does not block.

| Rule | Severity | Why | | ---- | -------- | --- | | manifest | error | A structural problem in the manifest. | | action-script-module | error | The runner has no module system — inline what you need. | | action-script-react | error | React and SDK hooks are the widget surface. | | action-script-jsx | error | JSX renders nothing here. | | action-script-dom | error | There is no DOM in the runner. | | action-script-host | error | No Node or host-escape globals (process, eval, …). | | action-script-no-globals | warning | The script touches no runner global — probably a stub. | | action-script-missing-await | warning | An async API called with no await/.then — the run finishes before the work does, and reports success. |

Banned words inside comments and string literals are not flagged, so a script that logs "closing the window" lints clean.

pack lints first and refuses to write anything if there are errors, then writes appstudio.action.json (the contract the marketplace reads) and runs npm pack to produce the uploadable .tgz.

manifest.version is the Action's only version. Because npm names the tarball <name>-<version>.tgz from package.json, pack points package.json's version at the manifest's before packing (and says so) — bump the manifest and the .tgz you upload is named for the release it carries. package.json must exist in the packed directory; npm resolves one upward, so packing without it would tarball an ancestor package.

There is deliberately no dev command. The live-reload loop is a first-party internal surface; the external loop is lint → pack → publish to npm or upload the .tgz in the Developer dashboard → install in the workspace.

API

import { CONTRACT, validateManifest, validatePropertySchema } from "@colixsystems/action-sdk";

const result = validateManifest(manifest);
if (!result.ok) console.error(result.errors);

| Export | Purpose | | ------ | ------- | | CONTRACT | The frozen vocabulary — categories, property types, action triggers, globals, caps. | | validateManifest(manifest) | { ok: true } or { ok: false, errors: string[] }. | | canonicalCategory(category) | Uppercase category, or null. | | validatePropertySchema(schema, label?) | Validates a propertySchema on its own. | | validateHiddenProperties(hidden, opts?) | Validates the values YOU ship that the workspace never sees. A name also in propertySchema is refused. | | hiddenPropertyNames(hidden) | The hidden names a manifest ships. | | validatePropertyValues(schema, values, opts?) | Coerces what the OPERATOR typed. { ok, value, secrets } — secrets come back separately because they are stored encrypted; an undeclared key is rejected. | | requiredPropertyNames(schema) | The names that must hold a value before the Action can run. | | SECRET_PLACEHOLDER | What a stored secret reads back as; sending it means "keep it". | | secretPropertyNames(schema) | The property names holding credentials. | | actionTranslationKey(id, key) | The namespaced dictionary key. | | isSecretPropertyType(type) | Whether a property type holds a credential. | | lintManifest(manifest) | { ok, findings } — structure + every action script. | | lintActionScript(script, key) | Findings for one script on its own. | | totalScriptBytes(manifest) | UTF-8 size of every declared script. |

contract.cjs, manifest.cjs and property-schema.cjs hold the single implementation of each concern; the matching .js files are thin ESM re-exports, so the two module systems can never disagree.

License

MIT