@stacksjs/orm 0.75.40 → 0.75.42
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.
- package/dist/define-model.d.ts +8 -0
- package/dist/gdpr/engine.d.ts +82 -0
- package/dist/gdpr/index.d.ts +20 -0
- package/dist/gdpr/models.d.ts +9 -0
- package/dist/gdpr/plan.d.ts +12 -0
- package/dist/gdpr/register.d.ts +30 -0
- package/dist/gdpr/types.d.ts +88 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +16 -11
- package/dist/model-registry.js +1 -1
- package/dist/models/Activity.ts +2 -0
- package/dist/models/CampaignSend.ts +2 -0
- package/dist/models/Card.ts +1 -0
- package/dist/models/CardComment.ts +1 -0
- package/dist/models/Comment.ts +5 -0
- package/dist/models/CommunicationSuppression.ts +2 -1
- package/dist/models/ConsentEvent.ts +4 -3
- package/dist/models/Content/Author.ts +5 -0
- package/dist/models/EmailListSubscriber.ts +1 -0
- package/dist/models/EmailSuppression.ts +2 -0
- package/dist/models/Forms/FormSubmission.ts +5 -0
- package/dist/models/GdprRequest.ts +77 -0
- package/dist/models/MagicLinkToken.ts +2 -0
- package/dist/models/Notification.ts +2 -0
- package/dist/models/NotificationDelivery.ts +4 -0
- package/dist/models/PaymentMethod.ts +5 -0
- package/dist/models/PaymentTransaction.ts +1 -0
- package/dist/models/PersonalAccessToken.ts +3 -0
- package/dist/models/Referral.ts +4 -1
- package/dist/models/ReferralCode.ts +4 -1
- package/dist/models/Request.ts +3 -0
- package/dist/models/SmsOptOut.ts +2 -0
- package/dist/models/SocialAccount.ts +3 -0
- package/dist/models/SocialPost.ts +2 -0
- package/dist/models/StorageItemTask.ts +14 -8
- package/dist/models/Subscriber.ts +2 -0
- package/dist/models/SubscriberEmail.ts +2 -0
- package/dist/models/Subscription.ts +1 -0
- package/dist/models/TeamInvitation.ts +2 -0
- package/dist/models/TeamMember.ts +1 -0
- package/dist/models/User.ts +5 -0
- package/dist/models/commerce/Bid.ts +3 -0
- package/dist/models/commerce/Cart.ts +2 -0
- package/dist/models/commerce/Courier.ts +7 -0
- package/dist/models/commerce/CourierPing.ts +3 -0
- package/dist/models/commerce/Customer.ts +5 -0
- package/dist/models/commerce/DeliveryStop.ts +7 -0
- package/dist/models/commerce/GiftCard.ts +4 -0
- package/dist/models/commerce/Order.ts +5 -0
- package/dist/models/commerce/OrderItem.ts +2 -0
- package/dist/models/commerce/Payment.ts +5 -0
- package/dist/models/commerce/Pledge.ts +3 -0
- package/dist/models/commerce/Transaction.ts +2 -0
- package/dist/models/commerce/WaitlistProduct.ts +5 -0
- package/dist/models/commerce/WaitlistRestaurant.ts +4 -0
- package/dist/routes.js +1 -1
- package/package.json +26 -26
package/dist/define-model.d.ts
CHANGED
|
@@ -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';
|