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

@recordtimelabel/core

v0.6.20

Published

Shared RecordTimeLabel data model, merge logic, operations, and sync engine.

Downloads

2,189

Readme

@recordtimelabel/core

Shared RecordTimeLabel data model, normalization, merge logic, operation reducer, and sync engine.

Scope

This package is intentionally platform-neutral. It must not import React, Firebase, Chrome extension APIs, Electron APIs, or app-specific UI code.

Current consumers:

  • D:\Programming\RecordTimeLabel
  • D:\Programming\YouTwitchDownloader

During local development an app can consume a sibling checkout with:

"@recordtimelabel/core": "file:../recordtimelabel-core"

For release builds, consume a fixed npm package, git tag, or private registry version so builds do not depend on a sibling folder path. The release target is 0.6.20; verify that its registry tarball and lockfile integrity are available before updating consumers:

"@recordtimelabel/core": "0.6.20"

If this checkout's package.json is ahead of the published version, publish the new package before updating consumers to that version.

Ordinary cold sync uses bounded operations and per-operation acknowledgements; it never promotes pending work to a snapshot replacement.

Public API

  • normalizeState(input)
  • normalizeRecords(records, options)
  • mergeLocalRemote({ localState, remoteState, pendingOps, clientId, now })
  • applyOperation(state, operation)
  • applyRecordTimeLabelOperation(state, operation)
  • createRecordTimeLabelSyncEngine({ storage, cloud, session, client, clock, logger })
  • RTL_MAX_SYNC_DRAIN_ROUNDS
  • RTL_SYNC_DRAIN_RETRY_DELAY_MS
  • RECORD_TIMELABEL_DURABLE_ENGINE_CAPABILITIES
  • RECORD_TIMELABEL_CAPABILITY_CLOUD_FAILURE_STATE
  • RECORD_TIMELABEL_CLOUD_FAILURE_CLASSES
  • RECORD_TIMELABEL_CLOUD_RECOVERY_ACTIONS
  • normalizeRecordTimeLabelCloudFailure(input)
  • classifyRecordTimeLabelCloudRecovery(input)
  • toRecordTimeLabelCloudFailureError(input)
  • RECORD_TIMELABEL_PROTOCOL_CAPABILITIES
  • toRecordTimeLabelWireOperation(operation)
  • buildRecordTimeLabelRequestId(namespace, operations)
  • createRecordTimeLabelTransportFailureResults(operations, failure, options)
  • normalizeRecordTimeLabelOperationResults(operations, results, options)
  • normalizeRecordTimeLabelEnvelopeResponse(operations, response, options)
  • createSyncEngine({ storageAdapter, cloudAdapter, clientId, clock, logger })
  • createRecordTimeLabelController({ storageAdapter, cloudAdapter, clientId, settingKeys, clock, logger })
  • buildRecordTimeLabelSyncPayload({ data, previousCloudData, pendingOps, syncMode, clientId, now, groupOrderNormalizer })
  • buildSyncPayloadWithOperations({ data, previousCloudData, pendingOps, syncMode, clientId, now, groupOrderNormalizer })
  • flushPendingOperations({ operations, storageAdapter, cloudAdapter, clientId, now })
  • mergeRemoteStateIntoLocal({ localData, remoteData, pendingOps, settingKeys, clientId, now })
  • buildLocalStateFromStorage(data, options)
  • buildStoragePatchFromState(state, options)
  • buildFirestoreV1UserPatch(state, options)
  • buildFirestoreV2LogicalPaths(userId)
  • buildFirestoreV2DocumentsFromState(state, options)
  • buildFirestoreV2DocumentChangeSet(previousDocuments, nextDocuments, options)
  • buildStateFromFirestoreV2Documents(documents, options)
  • validateRecordTimeLabelOperationBatch(body)
  • buildFirestoreV2OperationReadPlan(operations)
  • extendFirestoreV2OperationReadPlanWithRecords(readPlan, records)
  • extendFirestoreV2OperationReadPlanWithTrash(readPlan, trashEntries)
  • planFirestoreV2OperationChanges({ documents, operations, localState, now, requireLifecycleGeneration })
  • estimateFirestoreV2WriteUnits(changes, overhead)
  • buildOperationsFromSnapshotDiff({ previousState, nextState, now, operationIdPrefix, batchSize })(一般操作在 batchesfolder.deletebulkOperations
  • RTL_SYNC_PROTOCOL_VERSION
  • hasMeaningfulRecordTimeLabelCloudState(data, options)
  • buildRecordTimeLabelContentFingerprint(data)
  • normalizeRecordTimeLabelDomainState(input)
  • migrateRecordTimeLabelExpandedGroups({ pendingOperations, currentView })
  • buildMigratedRecordTimeLabelV2State(legacyState, options)
  • RECORD_TIMELABEL_CLOUD_SCHEMAS
  • RECORD_TIMELABEL_SYNC_MODES
  • createOperation(type, payload, options)
  • OPERATION_TYPES
  • getActiveTrashEntries(entries, now)
  • RTL_TRASH_RETENTION_MS
  • buildFirestoreV2SnapshotRoot({ currentRoot, incomingRoot, mode })
  • applyRecordTimeLabelSnapshot(currentState, incomingState, mode)
  • composeRecordTimeLabelHydratedState({ remote, pendingOps, importJobs, localNavigation })
  • applyFirestoreV2ResolvedChangeBatch({ cache, changes, resolvedDocuments, targetRevision })
  • FIRESTORE_V2_BOOTSTRAP_REASONS

The package exports five intentional entrypoints. The root (@recordtimelabel/core) keeps the complete backwards-compatible surface, /protocol contains only shared acknowledgement protocol helpers, /firestore-v2 contains platform-neutral document and operation planners, /domain contains group identity, Twitch VOD matching, legacy import, and channel-folder planning, and /compat contains the legacy createSyncEngine and createRecordTimeLabelController APIs.

RECORD_TIMELABEL_DOMAIN_CAPABILITIES is a local release contract. It is not a gateway protocol capability.

Durable sync engine

createRecordTimeLabelSyncEngine is the platform-neutral durable workspace API. Its storage port has load() and save(workspace) methods (adapters may consume the optional second save(workspace, fenceContext) argument to enforce the session/epoch atomically), and its cloud port has bootstrap(context, attemptOptions), applyOperations(envelope, context), and subscribe(listener, context) methods. The session port provides current(), subscribe(listener), and isCurrent(sessionToken, uid, workspaceEpoch). sessionToken is an opaque fence for equality only. Core never aliases current.id or the whole session object as an authorization token, and bootstrap/catchUp/apply context no longer carries token. Platform adapters obtain Firebase credentials from their own credential provider.

normalizeRecordTimeLabelCloudFailure preserves status, reason, code, message, retryable, and retryAfterMs, and assigns exactly one class: transient, bootstrap-required, auth-transition-required, terminal, or stale-session. Classification uses only an explicit class, HTTP status, retryable, bootstrapRequired, and other platform-neutral fields. Adapters may keep a Firebase reason string, but Core does not interpret Firebase or JWT taxonomies. {error: "reason"} keeps that string as reason, code, and message. Capability cloud-failure-state-v1 is part of RECORD_TIMELABEL_PROTOCOL_CAPABILITIES; clients must not invent that string. An auth-transition-required bootstrap or catch-up failure latches the captured session identity (sessionToken + UID + workspace epoch) so repeated init() calls do not invoke cloud.bootstrap again. A changed SessionPort identity, UID, or workspace epoch clears the latch. An ID token refresh that keeps the same sessionToken must not change session identity or current work. recoverBaseline falls back from catch-up only for explicit bootstrap-required / cursor-gap outcomes; auth and transient failures keep their class and must not start a second full walk.

The persisted workspace is versioned and contains only durable data:

{
  schemaVersion: 1,
  ownerUid: 'user-id',
  workspaceEpoch: 3,
  remoteBaseline: { state, revision: 12, changeCursor: 'cursor-12' },
  pendingOperations: [],
  rejectedOperations: {},
  syncMeta: {
    // Core-owned resume fence; the cursor itself remains opaque adapter data.
    changeCursorBinding: {
      ownerUid: 'user-id', uid: 'user-id', workspaceEpoch: 3,
      authSessionBinding: 'auth-session-3'
    }
  }
}

remoteBaseline.changeCursor is an opaque string owned by the cloud adapter and must remain byte-for-byte stable across persistence. Core stores the owner, workspace-epoch, and non-credential auth binding separately in syncMeta.changeCursorBinding so a committed workspace can be resumed without embedding session credentials in the cursor.

Every normalized pending operation also carries ownerUid and a non-negative workspaceEpoch. Dispatch stamps these fields from the captured session; legacy operations missing them inherit the migrated workspace identity. An operation whose explicit identity does not match the current workspace is moved to rejectedOperations with a stable identity-mismatch reason before any cloud apply.

Pending operations also carry an internal syncBatchId. Operations dispatched in one call share a batch boundary, while legacy pending operations are assigned one stable boundary during migration. The engine sends only the first FIFO batch boundary, capped at 20 operations, and derives a stable request ID from that boundary and the ordered canonical wire operations. Durable retry metadata does not affect request identity, while reusing an operation ID with different wire content is quarantined. A bounded history of completed operation fingerprints is kept in syncMeta; syncBatchId, identity fences, and retry scheduling fields stay outside canonical wire operations.

Bootstrap attempts use initial-hydration, rejection-rebase, or gap-recovery modes with a unique attemptId; all require a fresh logical attempt. Structured failures, missing state, and missing/invalid revisions fail closed. UID or workspace epoch changes clear the visible workspace and require init() before another dispatch or sync; token refresh for the same UID/epoch only refreshes the subscription fence. An operation acknowledgement records its revision diagnostically and advances remoteBaseline.revision with a complete state, or with an all-applied no-state ACK at exactly the next revision; larger revision gaps trigger a fresh bootstrap. Re-dispatching the same operation ID and wire content is reported as deduplicated, while different wire content is quarantined as an ID conflict. Rejected entries are terminal diagnostics in this API; an intentional retry currently requires a new operation ID.

The engine exposes init(), dispatch(operations), sync(reason), syncUntilIdle(reason, options), waitForIdle(), waitForRemoteReady(), getSnapshot(), subscribe(listener), and destroy(). A cloud subscribe(listener, context) disposer may carry a .ready promise. After init() settles, hosts can await waitForRemoteReady() to prove that the current subscription's initial root observation and any bounded catch-up have settled; this wait rejects when the listener fails before readiness and fails closed across a session generation change or destroy. init() deliberately does not await this promise because the initial listener callback may itself enter the engine queue. Anonymous/no-subscription use is immediately ready.

sync() preserves the one-FIFO-batch boundary. syncUntilIdle() drains subsequent batches up to RTL_MAX_SYNC_DRAIN_ROUNDS; it stops on retry/deferred/protocol conditions and reports syncDrainLimitReached as a retryable failure instead of silently reporting success. Hosts may provide beforeRound({round, pendingCount, snapshot}) to block gateway writes while a host-specific hydration/import gate is active, and runRound({round, pendingCount, run}) to serialize that gate check and the gateway call with host mutations. getSnapshot().state is derived by replaying pending operations over remoteBaseline.state; rejected operations are kept in diagnostics and are not replayed. Session tokens are used for fencing but are never persisted. The legacy createSyncEngine and app adapters remain available and are not implicitly migrated by this API.

expandedGroups is a local view projection, not durable cloud domain state. Durable workspaces, content fingerprints, and Firestore v2 root documents exclude it. The migration helper consumes legacy expandedGroups.update operations in order, returns the resulting view projection, and is idempotent; legacy root fields are preserved verbatim when a compatibility document is rewritten so older clients can continue reading the view until they migrate.

Operation-result protocol

The root package and @recordtimelabel/core/protocol export the shared acknowledgement normalizers. normalizeRecordTimeLabelOperationResults(operations, results) verifies count, request membership, unique IDs, and completeness before returning results in request order. The only normalized statuses are applied, noop, rejected, and retryable; retryable is derived from that status, while the legacy id and applied fields remain available alongside operationId, reason, and retryAfterMs.

normalizeRecordTimeLabelEnvelopeResponse(operations, response) requires an explicit operationResults array by default. Malformed, duplicate, missing, unknown, or ambiguous IDs throw stable protocol errors so callers can reject the entire acknowledgement before changing durable state.

Gateway-only compatibility code may opt into successful legacy envelopes with {allowLegacySuccessWithoutResults: true}. New durable clients never enable that fallback. Capabilities operation-conflict-quarantine, strict-operation-results, and lifecycle-generation-fence gate the corresponding 0.4.4 behavior. Single record.restore, folder.restore, and trash.purge operations require a positive expectedGeneration; batch lifecycle operations retain their existing contract. The gateway validator derives enforcement from client.capabilities. The planner is strict by default; the time-limited legacy gateway path must explicitly pass requireLifecycleGeneration: false for clients that did not declare the capability. A supplied but stale generation is always rejected.

Firestore v1 Compatibility

Phase 1 keeps the existing users/{uid} document shape:

  • folders
  • records
  • groupOrder
  • folderOrder
  • expandedGroups
  • settings

The core also normalizes and preserves these compatibility metadata fields:

  • rtlSyncMeta
  • deletedRecordTombstones
  • deletedFolderTombstones
  • trashEntries

Deletion must be represented by tombstones or operations. Do not reintroduce the old heuristic that treats "local exists but cloud missing for more than five minutes" as deletion.

mergeLocalRemote resolves deletions by lifecycleGeneration, in both directions: a tombstone or trash entry is dropped when either side carries that entity as active and newer, and restore operations are what bump the generation. When generations tie — an active copy written outside the delete/restore path — the active copy's own timestamp breaks the tie against deletedAt. A stale snapshot on either side therefore cannot re-delete an entity that was already restored.

Consumers must not reimplement this reconciliation in an app-level wrapper. In particular, never clear a local tombstone just because the remote snapshot still carries the entity as active: that is presence-based inference, the same anti-pattern as the five-minute heuristic above, and it is not order-independent, so two clients using different rules will not converge on shared cloud data.

Firestore v2 Gateway Contract

The package exposes platform-neutral v2 document helpers so app adapters can share the same record/folder/settings/ops shape before wiring Firebase SDK or REST calls:

  • root document users/{uid}/recordTimeLabel/main: settings, order arrays, tombstones, and sync metadata
  • users/{uid}/recordTimeLabel/main/records/{recordId}: flattened per-record documents with folderId
  • users/{uid}/recordTimeLabel/main/folders/{folderId}: per-folder documents
  • users/{uid}/recordTimeLabel/main/trash/{trashEntryId}: 30-day record/folder snapshots
  • users/{uid}/recordTimeLabel/main/lifecycleTombstones/{id}: content-free anti-revival markers
  • users/{uid}/recordTimeLabel/main/ops/{opId}: pending operation documents

The v2 validator, bounded read planner, pure mutation planner, cost estimator, and snapshot-diff batch builder are also platform-neutral. They do not import Firebase and do not perform network writes. Functions and clients must use this package instead of maintaining app-specific planner copies.

Test

npm test