Build on the supported package contract
Use @absolutejs/sync-pack-favorites through its supported public entry points.
@absolutejs/sync-pack-favoritesv0.2.4betaData & SyncPer-actor saved-resources pack for @absolutejs/sync — favorite/unfavorite/pin/list with optional join to host resources
bun add @absolutejs/sync-pack-favoritesPer-actor saved-resources pack for @absolutejs/sync. Each actor sees only their own rows; favoriting is idempotent (deterministic row id); optional join collection pairs each favorite with the host's resource row in one live subscription.
Then from the client:
Surface — Name — What it does
Collection — favorites — Per-actor list. Optional params.resourceKind filter
Mutation — favorites:favorite — Idempotent insert. Subsequent calls are no-ops
Mutation — favorites:unfavorite — Idempotent delete. No-op if the row never existed
Mutation — favorites:toggle — Insert if missing, delete if present. Returns { favorited }
Collection — favorites-with-resource (opt) — Join with the host's resource table — see below
The deterministic id means the same (actor, kind, resource) triple always maps to the same row — so duplicate favorite calls are idempotent at the storage layer, not just behaviorally.
When you set joinResources, the pack additionally registers a join collection that pairs each favorite with the host's resource row (same pattern as comments-with-author from sync-pack-comments).
Inner join — favorites whose resource has been deleted drop out of the join (but stay in the base favorites collection).
Default: per-instance in-memory store. For a persistent backend, pass a custom store:
Pass a prefix to coexist with another favorites pack instance (e.g. a "team" set vs a "private" set):
Outcomes
Use @absolutejs/sync-pack-favorites through its supported public entry points.
Hardening checklist
Follow in order
# @absolutejs/sync-pack-favorites
bun add @absolutejs/sync-pack-favoritesWorking example for Usage.
import { createSyncEngine } from '@absolutejs/sync/engine';
import { createFavoritesPack } from '@absolutejs/sync-pack-favorites';
const engine = createSyncEngine();
engine.registerPack(
createFavoritesPack({
getActorId: (ctx) => ctx.session.userId,
}),
);Then from the client:
// Toggle is the easy default — one round-trip, returns the new state.
const { favorited } = await store.mutate({
args: { resourceKind: 'doc', resourceId: 'doc-123' },
name: 'favorites:toggle',
});
// Or favorite / unfavorite explicitly.
await store.mutate({ args: { resourceKind: 'doc', resourceId: 'doc-123' }, name: 'favorites:favorite' });
await store.mutate({ args: { resourceKind: 'doc', resourceId: 'doc-123' }, name: 'favorites:unfavorite' });Working example for Row shape.
type FavoriteRow = {
id: string; // `${actorId}:${resourceKind}:${resourceId}` — deterministic
actorId: string;
resourceKind: string; // app-level: "doc" | "task" | "issue" | ...
resourceId: string;
createdAt: number;
};When you set joinResources, the pack additionally registers a join collection that pairs each favorite with the host's resource row (same pattern as comments-with-author from sync-pack-comments).
type DocRow = { id: string; title: string };
engine.registerReader('docs', { all: () => db.docs.list() });
engine.registerPack(
createFavoritesPack<MyCtx, DocRow>({
getActorId,
joinResources: {
table: 'docs',
// Default (row) => row.id; override if your resource id key isn't `id`.
// key: (doc) => doc.docId,
// Required: host supplies the resource-side hydrate.
hydrate: () => db.docs.list(),
},
}),
);
const view = await engine.subscribe<
FavoriteRow & { resource: DocRow },
{ resourceKind?: string }
>({
collection: 'favorites-with-resource',
params: { resourceKind: 'doc' },
ctx,
onDiff: rerender,
});
// view.initial[0] === { ...favorite, resource: { id, title } }Supported entry points declared by this package manifest.
Package entry point declared in package.json.
Scripts declared by this package manifest.
Search the declarations exported by the current package type files. Expand a symbol to inspect its source-backed signature.
A single favorited resource for a single actor.
type FavoriteRow = {
/** `${actorId}:${resourceKind}:${resourceId}` — deterministic so toggling is idempotent. */
id: string;
actorId: string;
/** App-level resource type ("doc", "task", "issue", ...). Lets one inbox span multiple kinds. */
resourceKind: string;
resourceId: string;
createdAt: number;
/**
* When the actor pinned this favorite, or `null` when unpinned.
* Clients can sort pinned-first by descending `pinnedAt`. Set by the
* `favorites:pin` / `favorites:togglePin` mutations.
*/
pinnedAt: number | null;
};@absolutejs/sync-pack-favorites