Skip to main content

@Snapshotable - overview

The @Snapshotable() class decorator adds the instance method takeSnapshot to the decorated Dream model. When called, takeSnapshot builds a plain object of all the database fields for the model and recursively traverses its association tree, following HasMany and HasOne associations. It is designed for internal use cases — retention archiving, compliance storage, internal audit trails. It is not appropriate for responding to user data subject access requests (e.g. GDPR/CCPA "give me all my data"); the output reflects your internal association graph, not what a user would consider "their data."

Auto-inclusion is the point​

Snapshotable automatically captures new associations as your schema evolves. For retention use cases, omission is the real compliance risk: you don't want a new association silently excluded from archived snapshots just because it was added after the initial implementation. The tool errs toward capturing more rather than less.

Data boundaries​

BelongsTo associations are intentionally skipped, as are through associations, so Snapshotable automatically avoids circuits. Starting from a root model, traversal can only reach records that model owns — it cannot jump across a BelongsTo to a different owner. To explicitly include a through association, decorate it with the @SnapshotableFollowThrough() decorator.

Many-to-many join models (e.g. UserGroup joining User and Group) are safe by default: the traversal includes the join records but stops there since join models have only BelongsTo associations. If you do opt in via @SnapshotableFollowThrough() and reach a shared resource (e.g. a Group), that resource's hasMany back to the join model will pull in join records for all owners of that resource — not their core records (still blocked by BelongsTo), but the join records. For internal retention this is generally acceptable. If the join model carries sensitive metadata, apply @SnapshotableIgnore() to that association on the destination model.

Other behaviors​

Associations with a DreamConst.required or DreamConst.passthrough and-clause must be decorated with @SnapshotableIgnore(). These associations cannot be preloaded without a runtime value they require, so takeSnapshot() throws if it encounters one that hasn't been excluded.

Soft-deleted records are included in snapshots — the soft-delete default scope is removed so that deleted children appear in the output. This is intentional: for retention use cases, the data existed and should be captured.

Self-referential models (e.g. a TreeNode with HasMany children) are supported. The same model class can appear up to four times in a single preload path, which handles trees up to depth four with no extra queries; deeper levels fall back to on-demand loading.

NOTE: Snapshotable uses batched preload queries to avoid N+1 queries for trees up to 4 levels deep, and falls back to on-demand loading beyond that. It will also leverage the read replica, if configured. Snapshotable builds a single object of the entire content tree, so the in-memory object may become quite large. As such, it may be advisable to leverage @SnapshotableIgnore to prevent full traversal of the tree so that it can be split into chunks. It is recommended that Snapshotable is only used in a background job (see https://psychicframework.com/docs/plugins/workers/overview).

tip
  • To learn how to install and configure Snapshotable, see the usage guide.