@stacksjs/orm 0.75.41 → 0.75.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/define-model.d.ts +8 -0
  2. package/dist/gdpr/engine.d.ts +82 -0
  3. package/dist/gdpr/index.d.ts +20 -0
  4. package/dist/gdpr/models.d.ts +9 -0
  5. package/dist/gdpr/plan.d.ts +12 -0
  6. package/dist/gdpr/register.d.ts +30 -0
  7. package/dist/gdpr/types.d.ts +88 -0
  8. package/dist/index.d.ts +5 -0
  9. package/dist/index.js +16 -11
  10. package/dist/model-registry.js +1 -1
  11. package/dist/models/Activity.ts +2 -0
  12. package/dist/models/CampaignSend.ts +2 -0
  13. package/dist/models/Card.ts +1 -0
  14. package/dist/models/CardComment.ts +1 -0
  15. package/dist/models/Comment.ts +5 -0
  16. package/dist/models/CommunicationSuppression.ts +2 -1
  17. package/dist/models/ConsentEvent.ts +4 -3
  18. package/dist/models/Content/Author.ts +5 -0
  19. package/dist/models/EmailListSubscriber.ts +1 -0
  20. package/dist/models/EmailSuppression.ts +2 -0
  21. package/dist/models/Forms/FormSubmission.ts +5 -0
  22. package/dist/models/GdprRequest.ts +77 -0
  23. package/dist/models/MagicLinkToken.ts +2 -0
  24. package/dist/models/Notification.ts +2 -0
  25. package/dist/models/NotificationDelivery.ts +4 -0
  26. package/dist/models/PaymentMethod.ts +5 -0
  27. package/dist/models/PaymentTransaction.ts +1 -0
  28. package/dist/models/PersonalAccessToken.ts +3 -0
  29. package/dist/models/Referral.ts +4 -1
  30. package/dist/models/ReferralCode.ts +4 -1
  31. package/dist/models/Request.ts +3 -0
  32. package/dist/models/SmsOptOut.ts +2 -0
  33. package/dist/models/SocialAccount.ts +3 -0
  34. package/dist/models/SocialPost.ts +2 -0
  35. package/dist/models/StorageItemTask.ts +14 -8
  36. package/dist/models/Subscriber.ts +2 -0
  37. package/dist/models/SubscriberEmail.ts +2 -0
  38. package/dist/models/Subscription.ts +1 -0
  39. package/dist/models/TeamInvitation.ts +2 -0
  40. package/dist/models/TeamMember.ts +1 -0
  41. package/dist/models/User.ts +5 -0
  42. package/dist/models/commerce/Bid.ts +3 -0
  43. package/dist/models/commerce/Cart.ts +2 -0
  44. package/dist/models/commerce/Courier.ts +7 -0
  45. package/dist/models/commerce/CourierPing.ts +3 -0
  46. package/dist/models/commerce/Customer.ts +5 -0
  47. package/dist/models/commerce/DeliveryStop.ts +7 -0
  48. package/dist/models/commerce/GiftCard.ts +4 -0
  49. package/dist/models/commerce/Order.ts +5 -0
  50. package/dist/models/commerce/OrderItem.ts +2 -0
  51. package/dist/models/commerce/Payment.ts +5 -0
  52. package/dist/models/commerce/Pledge.ts +3 -0
  53. package/dist/models/commerce/Transaction.ts +2 -0
  54. package/dist/models/commerce/WaitlistProduct.ts +5 -0
  55. package/dist/models/commerce/WaitlistRestaurant.ts +4 -0
  56. package/dist/routes.js +1 -1
  57. package/package.json +24 -24
@@ -9,6 +9,7 @@ import { type OrmModelDefinition as BQBModelDefinition, type OrmModelStatic } fr
9
9
  import type { ApiMiddleware, DashboardModelOptions } from '@stacksjs/types';
10
10
  import type { BelongsToForeignKeys } from './model-types';
11
11
  import type { Faker } from '@stacksjs/faker';
12
+ import type { GdprTraitOptions, PersonalAttribute } from './gdpr/types';
12
13
  import type { InferRelationNames } from '@stacksjs/query-builder';
13
14
  import type { Validator } from '@stacksjs/validation';
14
15
  // Re-export types from bun-query-builder for convenience
@@ -104,6 +105,7 @@ export declare interface StacksModelDefinition extends Omit<BQBModelDefinition,
104
105
  readonly middleware?: ApiMiddleware
105
106
  }
106
107
  commentable?: boolean | object
108
+ gdpr?: GdprTraitOptions
107
109
  broadcastOn?: (model: any) => string[]
108
110
  broadcastWith?: (model: any) => Record<string, unknown>
109
111
  }
@@ -217,6 +219,12 @@ export type StacksModelAttribute = Omit<BQBModelAttribute, 'factory'> & {
217
219
  * value can depend on another: `(faker, { discountType }) => ...`.
218
220
  */
219
221
  factory?: (faker: Faker, attributes: Readonly<Record<string, unknown>>) => unknown
222
+ /**
223
+ * Personal data (stacksjs/stacks#365): exported by an access request,
224
+ * anonymized by erasure and retention. See `GdprTraitOptions` for the
225
+ * model-level half.
226
+ */
227
+ personal?: PersonalAttribute
220
228
  }
221
229
  declare type ModelDefinition = StacksModelDefinition;
222
230
  declare type ValidationRuleOf<TAttribute> = TAttribute extends { validation: { rule: infer TRule } } ? TRule : never;
@@ -0,0 +1,82 @@
1
+ import type { GdprErasureResult, GdprExport, GdprModels, GdprPruneResult, GdprSubjectIdentity } from './types';
2
+ /** Whether a table exists. Only a missing-table answer is "no"; anything else propagates. */
3
+ export declare function gdprTableExists(table: string, handle?: Handle): Promise<boolean>;
4
+ /** Find the user a request is about, by id, email or uuid. */
5
+ export declare function resolveGdprSubject(reference: number | string, options?: GdprRunOptions, handle?: Handle): Promise<GdprSubjectIdentity | null>;
6
+ /**
7
+ * Everything the subject's declared personal data amounts to, as one document.
8
+ *
9
+ * Per model: the matching rows, carrying only the declared personal columns
10
+ * plus the row's id, uuid and timestamps - never an undeclared column, so a
11
+ * column added later is not exported until somebody classifies it. Alongside
12
+ * the data, the purpose, basis and retention each model declares, which an
13
+ * Article 15 answer owes the subject as well. Records an `access` audit row.
14
+ */
15
+ export declare function exportSubjectData(reference: number | string, options?: GdprRunOptions): Promise<GdprExport>;
16
+ /**
17
+ * Erase a data subject, per each model's declaration.
18
+ *
19
+ * `delete` removes the matched rows, `anonymize` overwrites their personal
20
+ * columns, `keep` leaves them (and is still reported, so the operator sees
21
+ * what was retained and can say why). All of it in one transaction with the
22
+ * audit row, so an erasure is either recorded and complete or neither.
23
+ *
24
+ * Idempotent: a second run matches what the first left behind and finds
25
+ * nothing to change. A dry run reads the same rows and writes nothing, so its
26
+ * report is exactly what a real run would do.
27
+ */
28
+ export declare function eraseSubject(reference: number | string, options?: GdprEraseOptions): Promise<GdprErasureResult>;
29
+ /**
30
+ * End every session the subject still holds.
31
+ *
32
+ * After the commit, not inside it: the auth layer runs its own transactions,
33
+ * and on SQLite a second writer inside an open one waits on itself. Failing
34
+ * here propagates - the data is already erased, re-running is safe, and a
35
+ * reported success that left a token alive would be the worse outcome.
36
+ */
37
+ declare function revokeCredentials(subjectId: number | string): Promise<boolean>;
38
+ /**
39
+ * Apply every model's retention policy.
40
+ *
41
+ * Candidate rows are read with a string bound one day past the cutoff and then
42
+ * compared as dates. Stored timestamps come in two spellings - the database
43
+ * clock's `YYYY-MM-DD HH:MM:SS` and the framework's `YYYY-MM-DDTHH:MM:SS.sss`
44
+ * - and comparing either against the other as a string is off by up to a day
45
+ * in the direction of deleting too much. The wider bound can only over-select,
46
+ * and the date comparison removes what it over-selected.
47
+ *
48
+ * One transaction per model, so a large table does not hold a lock over the
49
+ * whole run, with a `retention` audit row when anything changed.
50
+ */
51
+ export declare function pruneRetainedData(options?: GdprPruneOptions): Promise<GdprPruneResult>;
52
+ /**
53
+ * Access, erasure and retention over the declared personal data
54
+ * (stacksjs/stacks#365).
55
+ *
56
+ * Every write goes through the rows a subject link MATCHED - primary keys read
57
+ * first, written second - never through a predicate re-evaluated at write
58
+ * time. That is what keeps another subject's rows out of reach: an erasure can
59
+ * only touch a row it has already shown belongs to the subject it was asked
60
+ * about, and a dry run reports exactly the rows a real run would change.
61
+ */
62
+ /** The audit ledger: one row per access request, erasure, and retention run that changed something. */
63
+ export declare const GDPR_AUDIT_TABLE: 'gdpr_requests';
64
+ export declare interface GdprRunOptions {
65
+ models?: GdprModels
66
+ actor?: string
67
+ }
68
+ export declare interface GdprEraseOptions extends GdprRunOptions {
69
+ dryRun?: boolean
70
+ revokeCredentials?: boolean
71
+ }
72
+ export declare interface GdprPruneOptions extends GdprRunOptions {
73
+ dryRun?: boolean
74
+ now?: Date
75
+ }
76
+ declare type Row = Record<string, unknown>;
77
+ // The tables here are named by model declarations at runtime, so the typed
78
+ // schema cannot describe them. One alias rather than a cast at every call.
79
+ declare type Handle = any;
80
+ export declare class GdprSubjectNotFoundError extends Error {
81
+ constructor(reference: number | string);
82
+ }
@@ -0,0 +1,20 @@
1
+ export type { GdprEraseOptions, GdprPruneOptions, GdprRunOptions } from './engine';
2
+ export type { GdprProcessingRegister, GdprRegisterEntry, GdprRegisterFormat } from './register';
3
+ export type * from './types';
4
+ /**
5
+ * Data-subject access, erasure, retention and the processing register, all
6
+ * read off the model declarations (stacksjs/stacks#365). See `./types` for
7
+ * the vocabulary a model declares.
8
+ */
9
+ export {
10
+ eraseSubject,
11
+ exportSubjectData,
12
+ GDPR_AUDIT_TABLE,
13
+ GdprSubjectNotFoundError,
14
+ gdprTableExists,
15
+ pruneRetainedData,
16
+ resolveGdprSubject,
17
+ } from './engine';
18
+ export { loadGdprModels } from './models';
19
+ export { assertGdprPlan, GDPR_SUBJECT_MODEL, GdprDeclarationError, gdprTableName, resolveGdprPlan } from './plan';
20
+ export { buildProcessingRegister, renderProcessingRegister } from './register';
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The model definitions GDPR requests run against: the framework defaults,
3
+ * with the app's own `app/Models` overriding them by name - the same merge the
4
+ * generated API routes and the OpenAPI spec read.
5
+ *
6
+ * A default whose table this app never migrated is still in the set; the
7
+ * engine reports it as skipped rather than querying a table that is not there.
8
+ */
9
+ export declare function loadGdprModels(): Promise<Record<string, any>>;
@@ -0,0 +1,12 @@
1
+ import type { GdprDeclarationProblem, GdprModelDefinition, GdprModels, GdprPlan } from './types';
2
+ export declare function gdprTableName(name: string, definition: GdprModelDefinition): string;
3
+ export declare function resolveGdprPlan(models: GdprModels): GdprPlan;
4
+ /** Throw every declaration problem at once, or return the plan untouched. */
5
+ export declare function assertGdprPlan(plan: GdprPlan): GdprPlan;
6
+ /** The model whose rows ARE the data subjects. */
7
+ export declare const GDPR_SUBJECT_MODEL: 'User';
8
+ /** Raised when a declaration cannot be honoured; carries every problem at once. */
9
+ export declare class GdprDeclarationError extends Error {
10
+ readonly problems: GdprDeclarationProblem[];
11
+ constructor(problems: GdprDeclarationProblem[]);
12
+ }
@@ -0,0 +1,30 @@
1
+ import type { GdprErasureAction, GdprLawfulBasis, GdprPlan } from './types';
2
+ export declare function buildProcessingRegister(plan: GdprPlan): GdprProcessingRegister;
3
+ export declare function renderProcessingRegister(register: GdprProcessingRegister, format?: GdprRegisterFormat): string;
4
+ /**
5
+ * The processing register: a record of what personal data the application
6
+ * holds, whose it is, why, and what happens to it (GDPR Article 30).
7
+ *
8
+ * Generated, never edited. A hand-kept register is accurate on the day it is
9
+ * written and wrong from the first migration after; deriving it from the same
10
+ * declarations the erasure and retention code reads means the document cannot
11
+ * describe behaviour the application does not have. The output is stable - no
12
+ * timestamp, everything sorted - so a committed copy can be checked for drift
13
+ * the way the other generated documents are (`buddy gdpr:register:check`).
14
+ */
15
+ export declare interface GdprRegisterEntry {
16
+ model: string
17
+ table: string
18
+ subject: string
19
+ personal: string[]
20
+ notExported: string[]
21
+ erasure: GdprErasureAction | null
22
+ retention: string | null
23
+ basis: GdprLawfulBasis | null
24
+ purpose: string | null
25
+ }
26
+ export declare interface GdprProcessingRegister {
27
+ entries: GdprRegisterEntry[]
28
+ unclassified: string[]
29
+ }
30
+ export type GdprRegisterFormat = 'markdown' | 'json';
@@ -0,0 +1,88 @@
1
+ import type { GdprErasureAction, GdprLawfulBasis, GdprRetentionAction, GdprRetentionPolicy, GdprSubject, GdprTraitOptions, PersonalAttribute, PersonalAttributeOptions } from '@stacksjs/types';
2
+ export type {
3
+ GdprErasureAction,
4
+ GdprLawfulBasis,
5
+ GdprRetentionAction,
6
+ GdprRetentionPolicy,
7
+ GdprSubject,
8
+ GdprTraitOptions,
9
+ PersonalAttribute,
10
+ PersonalAttributeOptions,
11
+ };
12
+ /** A model definition, as much of it as the GDPR layer reads. */
13
+ export declare interface GdprModelDefinition {
14
+ readonly name?: string
15
+ readonly table?: string
16
+ readonly primaryKey?: string
17
+ readonly belongsTo?: unknown
18
+ readonly traits?: Readonly<Record<string, unknown>> & { readonly gdpr?: GdprTraitOptions }
19
+ readonly attributes?: Readonly<Record<string, unknown>>
20
+ }
21
+ /** One personal column, resolved. */
22
+ export declare interface GdprPersonalField {
23
+ attribute: string
24
+ column: string
25
+ export: boolean
26
+ perRow: boolean
27
+ anonymizeTo: string | number | boolean | null
28
+ }
29
+ /** Everything the GDPR layer knows about one model. */
30
+ export declare interface GdprModelPlan {
31
+ model: string
32
+ table: string
33
+ primaryKey: string
34
+ subject: GdprSubjectLink | null
35
+ personal: GdprPersonalField[]
36
+ erasure: GdprErasureAction
37
+ retention: { days: number, column: string, action: GdprRetentionAction } | null
38
+ purpose: string | null
39
+ basis: GdprLawfulBasis | null
40
+ depth: number
41
+ exportColumns: string[]
42
+ }
43
+ export declare interface GdprDeclarationProblem {
44
+ model: string
45
+ message: string
46
+ }
47
+ export declare interface GdprPlan {
48
+ models: GdprModelPlan[]
49
+ unclassified: string[]
50
+ problems: GdprDeclarationProblem[]
51
+ }
52
+ /** The subject an access or erasure request is about. */
53
+ export declare interface GdprSubjectIdentity {
54
+ id: number | string
55
+ email: string | null
56
+ }
57
+ export declare interface GdprExport {
58
+ subject: { id: number | string }
59
+ generatedAt: string
60
+ data: Record<string, Array<Record<string, unknown>>>
61
+ processing: Record<string, { purpose: string | null, basis: GdprLawfulBasis | null, retentionDays: number | null }>
62
+ }
63
+ export declare interface GdprModelChange {
64
+ model: string
65
+ table: string
66
+ action: GdprErasureAction | GdprRetentionAction
67
+ matched: number
68
+ changed: number
69
+ fields: string[]
70
+ }
71
+ export declare interface GdprErasureResult {
72
+ subject: { id: number | string }
73
+ dryRun: boolean
74
+ changes: GdprModelChange[]
75
+ skipped: string[]
76
+ credentialsRevoked: boolean
77
+ }
78
+ export declare interface GdprPruneResult {
79
+ dryRun: boolean
80
+ cutoffs: Record<string, string>
81
+ changes: GdprModelChange[]
82
+ skipped: string[]
83
+ }
84
+ export type GdprModels = Readonly<Record<string, GdprModelDefinition>> | readonly GdprModelDefinition[];
85
+ /** How a model's rows are matched to a subject, resolved to columns. */
86
+ export type GdprSubjectLink = | { kind: 'column', columns: string[], where: Record<string, string | number | boolean> }
87
+ | { kind: 'via', model: string, foreignKey: string }
88
+ | { kind: 'email', column: string }
package/dist/index.d.ts CHANGED
@@ -94,6 +94,7 @@ export declare const Redirect: ReturnType<typeof lazyModel<typeof import('./mode
94
94
  export declare const Site: ReturnType<typeof lazyModel<typeof import('./models/Site').default>>;
95
95
  export declare const SiteDomain: ReturnType<typeof lazyModel<typeof import('./models/SiteDomain').default>>;
96
96
  export declare const SmsOptOut: ReturnType<typeof lazyModel<typeof import('./models/SmsOptOut').default>>;
97
+ export declare const GdprRequest: ReturnType<typeof lazyModel<typeof import('./models/GdprRequest').default>>;
97
98
  export declare const StorageItem: ReturnType<typeof lazyModel<typeof import('./models/StorageItem').default>>;
98
99
  export declare const StorageItemTask: ReturnType<typeof lazyModel<typeof import('./models/StorageItemTask').default>>;
99
100
  export declare const EmailIdempotency: ReturnType<typeof lazyModel<typeof import('./models/EmailIdempotency').default>>;
@@ -299,6 +300,10 @@ export * from './batch-loader';
299
300
  export * from './db';
300
301
  export * from './subquery';
301
302
  export * from './transaction';
303
+ // Data-subject access, erasure and retention, plus the processing register,
304
+ // read off each model's `personal` attributes and `gdpr` trait
305
+ // (stacksjs/stacks#365).
306
+ export * from './gdpr/index';
302
307
  export * from './model-types';
303
308
  export * from './types';
304
309
  export * from '@stacksjs/model-meta';