@qezor/userstore
v1.0.4
Published
Owner-scoped app data storage, tag indexing, and search helpers built for DynamoDB-style stores.
Maintainers
Readme
@qezor/userstore
@qezor/userstore is for owner-scoped app data that is:
- not identity
- not profile
- not accounting
It is useful for:
- user file refs
- temporary presigned URL records
- activity snapshots
- preferences and interests
- recommendation or algorithm signals
- app-specific user data
- records that belong to a user inside a wider team/workspace/org scope
It also supports tag-based discovery helpers like:
- users interested in
tech - users interested in both
techanddynamodb - listing all file records for a user
- listing all owner refs inside a workspace or group scope
- owner-scoped pagination with opaque cursors
- status-group counts over owner and scope records
- multi-scope membership-aware filtering
Installation
npm install @qezor/userstorePrecise Imports
const { ownerPk, recordSk, tagIndexPk, scopePk } = require("@qezor/userstore/keys")
const { encodeCursor, decodeCursor } = require("@qezor/userstore/cursor")
const { normalizeMemberships, hasMembership } = require("@qezor/userstore/memberships")
const { groupStatusCounts } = require("@qezor/userstore/status")
const { parseRecordSk, parseTagIndexSk, parseScopeIndexSk } = require("@qezor/userstore/keys")
const { normalizeTags } = require("@qezor/userstore/tags")
const { createOwnerRecord, createTagIndexRecords, createScopeIndexRecords } = require("@qezor/userstore/models")
const { createOwnerStoreRepository } = require("@qezor/userstore/store")
const { createOwnerSearchRepository } = require("@qezor/userstore/search")Record Example
const record = createOwnerRecord({
ownerType: "user",
ownerId: "u_123",
scopeType: "workspace",
scopeId: "workspace_9",
memberships: [
{ scopeType: "org", scopeId: "org_7" },
{ scopeType: "project", scopeId: "project_2" },
],
namespace: "drive",
kind: "file_ref",
recordId: "file_1",
createdAt: 1712345678,
updatedAt: 1712345678,
status: "active",
refs: {
fileId: "file_1",
objectKey: "avatars/u_123/a.png",
},
tags: ["files", "image"],
})Search Example
const owners = await search.findOwnersByAllTags("feed", ["tech", "dynamodb"])Query Recipes
await store.listRecordsByOwner("user", "u_123", {
namespace: "drive",
kind: "file_ref",
status: "active",
activeAt: Math.floor(Date.now() / 1000),
})
const page1 = await store.listRecordsByOwnerPage("user", "u_123", {
namespace: "drive",
kind: "file_ref",
limit: 25,
})
const page2 = await store.listRecordsByOwnerPage("user", "u_123", {
namespace: "drive",
kind: "file_ref",
limit: 25,
cursor: page1.nextCursor,
})
await store.listScopeRefs("workspace", "workspace_9", {
ownerType: "user",
kind: "file_ref",
status: "active",
})
await store.listScopeRefsAcrossScopes([
{ scopeType: "workspace", scopeId: "workspace_9" },
{ scopeType: "project", scopeId: "project_2" },
], {
ownerType: "user",
status: "active",
})
await store.countRecordsByStatus("user", "u_123", {
namespace: "drive",
kind: "file_ref",
pageLimit: 100,
maxPages: 25,
})
await search.findOwnerRecordsByAllTags("user", "u_123", "feed", ["tech", "dynamodb"], {
kind: "interest",
status: "active",
scopeType: "workspace",
scopeId: "workspace_9",
})
await search.findOwnersByAllTagsInScopes(
"feed",
["tech", "dynamodb"],
[
{ scopeType: "workspace", scopeId: "workspace_9" },
{ scopeType: "project", scopeId: "project_2" },
],
{ kind: "interest", status: "active" },
)
await search.countRecordsByStatusForAllTags("feed", ["tech", "dynamodb"], {
scopesAny: [{ scopeType: "workspace", scopeId: "workspace_9" }],
})Notes
- this package is for owner data and owner search
- billing, credits, invoices, and quota economics belong in
@qezor/ledgerkit - identity and profile belong in service-specific packages
- key parsing helpers are included so projected DynamoDB items can still be interpreted safely
scopeTypeandscopeIdlet records belong to an owner inside a broader container like a workspace, org, team, or projectmembershipslet one record participate in multiple scopes without losing direct owner identity- page methods return opaque
nextCursorvalues so consumers do not need DynamoDB key knowledge - count methods are explicit recipes for dashboards and quotas, and expose
truncated/nextCursorwhen page caps are reached
