Guide · 02

Your code is the API.

An app is collections of JSON records, values derived from them, and methods that read or change them. Only the methods you list are public.

An application is one TypeScript module built from three pieces:

SOURCE

collection

Named JSON records with string keys.

DEPENDENCY

derive

A synchronous function of records and other derived values. Always private.

BOUNDARY

query / mutation

Read a snapshot, or make one atomic change. Public only if you list it in http.

A complete example

Save this as examples/counter.ts:

examples/counter.ts
import { collection, define, derive, mutation, query } from "@flower-js/sdk";

const counters = collection<number>("counters");

const doubled = derive("counter.doubled", (ctx, id: string) =>
  (ctx.get(counters, id) ?? 0) * 2,
);

const increment = mutation("internal.counter.increment", (ctx, id: string) => {
  if (typeof id !== "string" || !id) throw new TypeError("An ID is required");
  ctx.set(counters, id, (ctx.get(counters, id) ?? 0) + 1);
  ctx.materialize(doubled, id);
  return ctx.get(doubled, id); // Includes the write above.
});

const get = query("internal.counter.get", (ctx, id: string) => {
  if (typeof id !== "string" || !id) throw new TypeError("An ID is required");
  return {
    count: ctx.get(counters, id) ?? 0,
    doubled: ctx.get(doubled, id),
  };
});

export default define({
  definitions: [doubled],
  http: {
    "counter.increment": increment,
    "counter.get": get,
  },
});

Deploy it and call it:

Deploy and call
node sdk/cli.ts deploy examples/counter.ts
node sdk/cli.ts call counter.increment '"visits"' --request-id visit-001
node sdk/cli.ts call counter.get '"visits"'
# value: { count: 1, doubled: 2 }
  • Only aliases listed in http are public. Everything else stays private.
  • Aliases don't have to match internal names.
  • Each deploy replaces the whole list of public methods.

What ctx can do

Queries can read; mutations can also write.

Database context operations
Read operationsMutation-only operations
ctx.get(collection, key)ctx.set(collection, key, value)
ctx.get(derived, args)ctx.delete(collection, key)
ctx.scan(collection)ctx.materialize(derived, args)
ctx.query(collection.by(index).eq(value))ctx.unmaterialize(derived, args)
ctx.range(collection.by(index).range({prefix, lte, limit}))Bounded pages over an index.
ctx.now()Throw to abort the whole mutation.
  • get returns null for a missing record.
  • scan returns {key, value} rows in key order.
  • Types aren't checked at runtime. Validate arguments yourself.

Start callbacks faster

If module setup doesn't depend on the call, add --initialization static to build or deploy. Flower initializes the module once and reuses it.

  • Globals and closures still reset on every call.
  • Memory and execution limits still apply.