@memberjunction/lists 0.0.1 → 5.37.0

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/README.md CHANGED
@@ -1,45 +1,309 @@
1
1
  # @memberjunction/lists
2
2
 
3
- ## ⚠️ IMPORTANT NOTICE ⚠️
3
+ Pure TypeScript core for MemberJunction Lists. Owns list materialization,
4
+ set algebra, delta computation, lineage tracking, refresh modes, drop
5
+ guards, sharing, and audience resolution. Framework-agnostic — no
6
+ GraphQL, HTTP, or Angular dependencies.
4
7
 
5
- **This package is created solely for the purpose of setting up OIDC (OpenID Connect) trusted publishing with npm.**
8
+ This is the **source of truth** for the type contracts that the rest of
9
+ the stack (the `ListOperationsResolver` in `@memberjunction/server`, the
10
+ `GraphQLListsClient` in `@memberjunction/graphql-dataprovider`, the
11
+ Phase-N list Actions in `@memberjunction/core-actions`, and the Angular
12
+ components in `@memberjunction/ng-list-management`) all import from.
6
13
 
7
- This is **NOT** a functional package and contains **NO** code or functionality beyond the OIDC setup configuration.
14
+ ## Installation
8
15
 
9
- ## Purpose
16
+ ```bash
17
+ npm install @memberjunction/lists
18
+ ```
10
19
 
11
- This package exists to:
12
- 1. Configure OIDC trusted publishing for the package name `@memberjunction/lists`
13
- 2. Enable secure, token-less publishing from CI/CD workflows
14
- 3. Establish provenance for packages published under this name
20
+ ## What's in here
15
21
 
16
- ## What is OIDC Trusted Publishing?
22
+ ```
23
+ ListOperations → materialize / refresh / compose / preview-and-apply
24
+ ListSharing → direct shares, email invitations, audit log, capabilities
25
+ AudienceResolver → friendly-named re-export of ResolveSource (Communications)
26
+ deltaToken → HMAC-signed preview-token contract (server enforced)
27
+ types → ListSource, ListDelta, ListCapabilities, etc.
28
+ ```
17
29
 
18
- OIDC trusted publishing allows package maintainers to publish packages directly from their CI/CD workflows without needing to manage npm access tokens. Instead, it uses OpenID Connect to establish trust between the CI/CD provider (like GitHub Actions) and npm.
30
+ ## Public API surface
19
31
 
20
- ## Setup Instructions
32
+ ### Core types (`types.ts`)
21
33
 
22
- To properly configure OIDC trusted publishing for this package:
34
+ ```typescript
35
+ type ListRefreshMode = 'Additive' | 'Sync';
23
36
 
24
- 1. Go to [npmjs.com](https://www.npmjs.com/) and navigate to your package settings
25
- 2. Configure the trusted publisher (e.g., GitHub Actions)
26
- 3. Specify the repository and workflow that should be allowed to publish
27
- 4. Use the configured workflow to publish your actual package
37
+ type ListSource =
38
+ | { kind: 'list'; listId: string }
39
+ | { kind: 'view'; viewId: string; runtimeParams?: Record<string, unknown> }
40
+ | { kind: 'adhoc'; entityName: string; extraFilter: string };
28
41
 
29
- ## DO NOT USE THIS PACKAGE
42
+ interface ResolvedRecordSet {
43
+ EntityName: string;
44
+ RecordIds: string[]; // MJ: List Detail.RecordID format
45
+ TotalCount?: number;
46
+ }
30
47
 
31
- This package is a placeholder for OIDC configuration only. It:
32
- - Contains no executable code
33
- - Provides no functionality
34
- - Should not be installed as a dependency
35
- - Exists only for administrative purposes
48
+ interface ListDelta {
49
+ TargetListId: string | null;
50
+ EntityName: string;
51
+ ToAdd: string[];
52
+ ToRemove: string[];
53
+ Unchanged: string[];
54
+ Counts: { Add; Remove; Unchanged; SourceTotal; TargetTotal };
55
+ Warnings: ListDeltaWarning[];
56
+ DeltaToken: string; // HMAC, 5-min TTL
57
+ }
36
58
 
37
- ## More Information
59
+ interface ApplyResult {
60
+ Success: boolean;
61
+ ResultCode: 'SUCCESS' | 'DROP_NOT_CONFIRMED' | 'STALE_DELTA' |
62
+ 'INVALID_TOKEN' | 'PERMISSION_DENIED' | 'TARGET_NOT_FOUND' |
63
+ 'PARTIAL_SUCCESS' | 'UNEXPECTED_ERROR';
64
+ Message: string;
65
+ CreatedListId?: string;
66
+ TargetListId?: string;
67
+ Counts?: { Added; Removed; Failed };
68
+ Errors?: string[];
69
+ }
70
+ ```
38
71
 
39
- For more details about npm's trusted publishing feature, see:
40
- - [npm Trusted Publishing Documentation](https://docs.npmjs.com/generating-provenance-statements)
41
- - [GitHub Actions OIDC Documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
72
+ ### `ListOperations`
42
73
 
43
- ---
74
+ Constructor: `new ListOperations(contextUser, provider?)`.
44
75
 
45
- **Maintained for OIDC setup purposes only**
76
+ ```typescript
77
+ // Read-only
78
+ ResolveSource(source: ListSource): Promise<ResolvedRecordSet>;
79
+ GetListMembers(listId: string): Promise<ResolvedRecordSet>;
80
+
81
+ // Preview (never mutates) — returns a signed DeltaToken
82
+ ComputeDelta(target: ListSource | 'new', source: ListSource, mode: ListRefreshMode): Promise<ListDelta>;
83
+ ComputeSetOp(op: 'union' | 'intersection' | 'difference', inputs: ListSource[], target?: ListSource | 'new'): Promise<ListDelta>;
84
+
85
+ // Commit (server enforces token validity + drop guard)
86
+ ApplyDelta(delta: ListDelta, opts: { ConfirmDrops: boolean; DeltaToken: string }): Promise<ApplyResult>;
87
+
88
+ // High-level convenience
89
+ MaterializeFromView(viewId: string, opts: MaterializeOptions): Promise<ApplyResult>;
90
+ AddViewResultsToList(viewId: string, listId: string): Promise<ApplyResult>;
91
+ RefreshFromSource(listId: string, mode: ListRefreshMode, opts: { ConfirmDrops: boolean }): Promise<ApplyResult>;
92
+ ```
93
+
94
+ #### The drop-row warning contract
95
+
96
+ Every mutating list operation goes through `ComputeDelta` first. The
97
+ returned `DeltaToken` is an HMAC over `{targetListId, source signature,
98
+ mode, timestamp}` with a 5-minute TTL.
99
+
100
+ `ApplyDelta` enforces, **server-side**, in this order:
101
+
102
+ 1. Token signature + 5-min TTL — else `INVALID_TOKEN`.
103
+ 2. Re-compute against current target state — else `STALE_DELTA`.
104
+ 3. `Counts.Remove > 0 && !ConfirmDrops` — else `DROP_NOT_CONFIRMED`.
105
+ 4. Editor permission on target (placeholder; Phase 2 wires the real
106
+ `ResourcePermission` check).
107
+
108
+ The Angular `<mj-list-delta-confirm>` dialog is the only UI path that
109
+ produces a valid token. The server rejects drops regardless of UI.
110
+
111
+ ### `ListSharing`
112
+
113
+ Constructor: `new ListSharing(contextUser, provider?)`.
114
+
115
+ ```typescript
116
+ type SharePermissionLevel = 'View' | 'Edit' | 'Owner';
117
+
118
+ type ShareTarget =
119
+ | { kind: 'user'; userId: string }
120
+ | { kind: 'role'; roleId: string };
121
+
122
+ // Direct shares (recipient already has an MJ account)
123
+ Share(args: { ListID; Target; PermissionLevel }): Promise<ShareResult>;
124
+ Unshare(permissionId: string): Promise<ShareResult>;
125
+ GetSharesForList(listId: string): Promise<ListShareSummary[]>;
126
+ GetListsSharedWithUser(): Promise<SharedListSummary[]>;
127
+
128
+ // Email invitations (recipient may not yet have an account)
129
+ Invite(args: { ListID; Email; Role: 'Editor' | 'Viewer'; TtlMs? }): Promise<InviteResult>;
130
+ AcceptInvitation(token: string): Promise<AcceptInvitationResult>;
131
+ RevokeInvitation(invitationId: string): Promise<ShareResult>;
132
+
133
+ // Permission introspection for UI gating
134
+ ResolveEffectivePermission(listId: string): Promise<SharePermissionLevel | null>;
135
+ ```
136
+
137
+ Every mutation also writes an `MJ: Audit Logs` entry. Audit log types
138
+ are seeded via metadata sync (see
139
+ [`metadata/audit-log-types/.list-sharing-audit-types.json`](../../metadata/audit-log-types/.list-sharing-audit-types.json)).
140
+
141
+ Direct user-shares fan out via the platform `CreateShareNotification`
142
+ dispatcher (registered by `@memberjunction/notifications`) — recipients
143
+ get in-app + email notifications per their preferences. Role-target
144
+ shares are not fanned out per-user (that's a separate worker).
145
+
146
+ ### Capability gating
147
+
148
+ UI components should derive button visibility from the caller's
149
+ effective permission level, not from raw entity-permission flags. The
150
+ mapping is centralized so UI and server agree:
151
+
152
+ ```typescript
153
+ const level: SharePermissionLevel | null =
154
+ await sharing.ResolveEffectivePermission(listId);
155
+
156
+ const caps: ListCapabilities = capabilitiesForLevel(level);
157
+ // caps.CanEdit / CanRefresh / CanShare / CanDelete / CanRunOperations
158
+ // Server-side enforcement remains the source of truth — these are a
159
+ // UX convenience so users don't see buttons they'll be rejected on.
160
+ ```
161
+
162
+ Mapping per mockup 19:
163
+
164
+ | Level | Read | Edit | Refresh | Share | Delete | Operations |
165
+ | ------- | :--: | :--: | :-----: | :---: | :----: | :--------: |
166
+ | Owner | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
167
+ | Editor | ✓ | ✓ | ✓ | ✗ | ✗ | ✓ |
168
+ | Viewer | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ |
169
+ | null | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
170
+
171
+ ### `AudienceResolver`
172
+
173
+ Friendly-named entry point for resolving an `AudienceSource` (= `ListSource`)
174
+ to a `ResolvedRecordSet`. Identical semantics to
175
+ `ListOperations.ResolveSource`, exposed under a name that reads
176
+ naturally for Communications and other audience-aware code.
177
+
178
+ ```typescript
179
+ const resolver = new AudienceResolver(contextUser, provider);
180
+ const result = await resolver.Resolve({ kind: 'list', listId });
181
+ // result.RecordIds — array of record IDs in the target audience
182
+ ```
183
+
184
+ ### `deltaToken`
185
+
186
+ Internal helpers exposed for resolver-layer use. Server bootstrap must
187
+ set the HMAC secret:
188
+
189
+ ```typescript
190
+ import { SetDeltaTokenSecret } from '@memberjunction/lists';
191
+
192
+ SetDeltaTokenSecret(process.env.MJ_LIST_DELTA_SECRET);
193
+ ```
194
+
195
+ Falls back to the env var `MJ_LIST_DELTA_SECRET` if no explicit call is
196
+ made. Refuses to use a default secret — silent fallback would let a
197
+ misconfigured server hand out forgeable tokens.
198
+
199
+ ## Examples
200
+
201
+ ### Save a view's current results as a new list with lineage
202
+
203
+ ```typescript
204
+ const ops = new ListOperations(currentUser, provider);
205
+ const result = await ops.MaterializeFromView('view-abc', {
206
+ ListName: 'Q4 VIP Donors',
207
+ Description: 'Materialized from the Q4 Donor View',
208
+ CategoryId: 'cat-marketing',
209
+ RememberLineage: true, // can refresh from source later
210
+ UseSnapshot: false, // refresh re-reads the live view
211
+ RefreshMode: 'Additive', // default refresh mode for this list
212
+ });
213
+ // result.CreatedListId, result.Counts.Added
214
+ ```
215
+
216
+ ### Refresh a lineage-bearing list (Sync mode, will drop missing rows)
217
+
218
+ ```typescript
219
+ const result = await ops.RefreshFromSource(listId, 'Sync', {
220
+ ConfirmDrops: true, // required when Sync mode would remove records
221
+ });
222
+ ```
223
+
224
+ ### Preview a 3-way intersection without committing
225
+
226
+ ```typescript
227
+ const delta = await ops.ComputeSetOp('intersection', [
228
+ { kind: 'list', listId: 'list-a' },
229
+ { kind: 'view', viewId: 'view-b' },
230
+ { kind: 'view', viewId: 'view-c' },
231
+ ]);
232
+ // delta.ToAdd has the resulting record IDs; nothing has been saved.
233
+ ```
234
+
235
+ ### Share a list with an Editor
236
+
237
+ ```typescript
238
+ const sharing = new ListSharing(currentUser, provider);
239
+ const result = await sharing.Share({
240
+ ListID: 'list-abc',
241
+ Target: { kind: 'user', userId: 'user-xyz' },
242
+ PermissionLevel: 'Edit',
243
+ });
244
+ // In-app + email notification fires automatically via the platform handler.
245
+ ```
246
+
247
+ ### Invite someone by email
248
+
249
+ ```typescript
250
+ const inv = await sharing.Invite({
251
+ ListID: 'list-abc',
252
+ Email: 'recipient@example.com',
253
+ Role: 'Editor',
254
+ TtlMs: 7 * 24 * 60 * 60 * 1000,
255
+ });
256
+ // inv.Token + inv.ExpiresAt — the caller builds the accept URL.
257
+ ```
258
+
259
+ ## Architecture
260
+
261
+ Sits at the bottom of a four-layer stack:
262
+
263
+ ```
264
+ ┌────────────────────────────────────────────────────────────┐
265
+ │ Angular UI (@memberjunction/ng-list-management, │
266
+ │ @memberjunction/ng-explorer-core, etc.) │
267
+ └────────────────────────────┬───────────────────────────────┘
268
+ ↓ uses
269
+ ┌────────────────────────────────────────────────────────────┐
270
+ │ GraphQLListsClient (@memberjunction/graphql-dataprovider) │
271
+ └────────────────────────────┬───────────────────────────────┘
272
+ ↓ wire format
273
+ ┌────────────────────────────────────────────────────────────┐
274
+ │ ListOperationsResolver (@memberjunction/server) │
275
+ └────────────────────────────┬───────────────────────────────┘
276
+ ↓ delegates to
277
+ ┌────────────────────────────────────────────────────────────┐
278
+ │ ListOperations / ListSharing / AudienceResolver ← this │
279
+ │ (@memberjunction/lists) │
280
+ └────────────────────────────────────────────────────────────┘
281
+ ```
282
+
283
+ Every layer is a thin pass-through. The Actions in
284
+ `@memberjunction/core-actions` (`MaterializeListFromViewAction`,
285
+ `ShareListAction`, etc.) are parallel consumers — they import directly
286
+ from this package so AI agents, workflows, and scheduled jobs get the
287
+ same capability as the UI.
288
+
289
+ ## Dependencies
290
+
291
+ - `@memberjunction/core` — `Metadata`, `RunView`, `UserInfo`, `IMetadataProvider`
292
+ - `@memberjunction/core-entities` — `MJListEntity`, `MJResourcePermissionEntity`, etc.
293
+ - `@memberjunction/global` — class registration utilities
294
+
295
+ No HTTP, no GraphQL, no Angular.
296
+
297
+ ## Testing
298
+
299
+ ```bash
300
+ cd packages/Lists
301
+ npm run test
302
+ ```
303
+
304
+ 76 vitest cases across `deltaToken` (HMAC roundtrip + secret resolution),
305
+ `ListOperations` (math, set-ops, drop guard, materialize, refresh modes,
306
+ snapshot vs live), `AudienceResolver`, and `ListSharing` (share / invite
307
+ / accept / revoke lifecycle, capability mapping).
308
+
309
+ Coverage tracked at 88%+ lines; reports via `npm run test:coverage`.
@@ -0,0 +1,22 @@
1
+ import type { IMetadataProvider, UserInfo } from '@memberjunction/core';
2
+ import type { AudienceSource, ResolvedRecordSet } from '@memberjunction/lists-base';
3
+ /**
4
+ * Friendly-named entry point for resolving an `AudienceSource` to a
5
+ * `ResolvedRecordSet`. Thin re-export over `ListOperations.ResolveSource`
6
+ * — semantically identical, but callers reading audience-related code
7
+ * don't need to know about the list-operations machinery underneath.
8
+ *
9
+ * Stateless on purpose; rebuild per call. Multi-provider safe: pass the
10
+ * provider through and the underlying `ListOperations` will scope to it.
11
+ */
12
+ export declare class AudienceResolver {
13
+ private readonly contextUser;
14
+ private readonly provider;
15
+ constructor(contextUser: UserInfo, provider?: IMetadataProvider);
16
+ /**
17
+ * Resolve an `AudienceSource` to the record IDs it represents +
18
+ * the entity those IDs belong to. Never mutates.
19
+ */
20
+ Resolve(source: AudienceSource): Promise<ResolvedRecordSet>;
21
+ }
22
+ //# sourceMappingURL=AudienceResolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AudienceResolver.d.ts","sourceRoot":"","sources":["../src/AudienceResolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACxE,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAIpF;;;;;;;;GAQG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAW;IACvC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgC;gBAE7C,WAAW,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,iBAAiB;IAK/D;;;OAGG;IACU,OAAO,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,iBAAiB,CAAC;CAIzE"}
@@ -0,0 +1,25 @@
1
+ import { ListOperations } from './ListOperations.js';
2
+ /**
3
+ * Friendly-named entry point for resolving an `AudienceSource` to a
4
+ * `ResolvedRecordSet`. Thin re-export over `ListOperations.ResolveSource`
5
+ * — semantically identical, but callers reading audience-related code
6
+ * don't need to know about the list-operations machinery underneath.
7
+ *
8
+ * Stateless on purpose; rebuild per call. Multi-provider safe: pass the
9
+ * provider through and the underlying `ListOperations` will scope to it.
10
+ */
11
+ export class AudienceResolver {
12
+ constructor(contextUser, provider) {
13
+ this.contextUser = contextUser;
14
+ this.provider = provider;
15
+ }
16
+ /**
17
+ * Resolve an `AudienceSource` to the record IDs it represents +
18
+ * the entity those IDs belong to. Never mutates.
19
+ */
20
+ async Resolve(source) {
21
+ const ops = new ListOperations(this.contextUser, this.provider);
22
+ return ops.ResolveSource(source);
23
+ }
24
+ }
25
+ //# sourceMappingURL=AudienceResolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AudienceResolver.js","sourceRoot":"","sources":["../src/AudienceResolver.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD;;;;;;;;GAQG;AACH,MAAM,OAAO,gBAAgB;IAI3B,YAAY,WAAqB,EAAE,QAA4B;QAC7D,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,OAAO,CAAC,MAAsB;QACzC,MAAM,GAAG,GAAG,IAAI,cAAc,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;QAChE,OAAO,GAAG,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;IACnC,CAAC;CACF"}
@@ -0,0 +1,215 @@
1
+ import { IMetadataProvider, UserInfo } from '@memberjunction/core';
2
+ import type { ApplyResult, ListDelta, ListRefreshMode, ListSource, MaterializeOptions, ResolvedRecordSet, SetOpKind } from '@memberjunction/lists-base';
3
+ /** Target argument shared by `ComputeDelta` / `ComputeSetOp`.
4
+ * Server-internal — not exposed on the public type surface because no
5
+ * consumer outside this package needs to compose it. */
6
+ export type DeltaTarget = ListSource | 'new';
7
+ /**
8
+ * Core list-operations engine. Pure-ish TypeScript: takes a `UserInfo` +
9
+ * optional `IMetadataProvider` and talks to data exclusively through
10
+ * `Metadata` / `RunView` / `BaseEntity`. No GraphQL, no HTTP, no Angular.
11
+ *
12
+ * Public methods are PascalCase per MJ convention. Internal helpers are
13
+ * camelCase. Every mutating method delegates through `ComputeDelta` →
14
+ * `ApplyDelta` to enforce the drop-row warning contract.
15
+ */
16
+ export declare class ListOperations {
17
+ private readonly contextUser;
18
+ private readonly provider;
19
+ constructor(contextUser: UserInfo, provider?: IMetadataProvider);
20
+ /**
21
+ * Resolve any `ListSource` to a concrete set of record IDs + the entity
22
+ * name those IDs belong to. Never mutates. Used by every higher-level
23
+ * operation (`ComputeDelta`, `ComputeSetOp`, audience resolution, etc.).
24
+ *
25
+ * Record IDs are returned in MJ List Detail format — single-PK entities
26
+ * use the raw value; composite-PK entities use the canonical
27
+ * `Field1|Value1||Field2|Value2` form (`CompositeKey.ToConcatenatedString`).
28
+ */
29
+ ResolveSource(source: ListSource): Promise<ResolvedRecordSet>;
30
+ /**
31
+ * Return the current members of a list as record IDs. Convenience wrapper
32
+ * over `ResolveSource({ kind: 'list', listId })`.
33
+ */
34
+ GetListMembers(listId: string): Promise<ResolvedRecordSet>;
35
+ /**
36
+ * Preview a refresh / materialization. Never mutates. Returns a fully
37
+ * populated `ListDelta` including a signed `DeltaToken` that `ApplyDelta`
38
+ * will accept (subject to TTL + re-computation + permission checks).
39
+ *
40
+ * - `target = 'new'`: equivalent to materializing a new list — every
41
+ * record in `source` becomes a `ToAdd`, no removals are possible.
42
+ * - `target = ListSource(kind:'list')`: a refresh of that list against
43
+ * `source`. `mode` controls whether removals are allowed (`Sync`) or
44
+ * forbidden (`Additive`).
45
+ */
46
+ ComputeDelta(target: DeltaTarget, source: ListSource, mode: ListRefreshMode): Promise<ListDelta>;
47
+ /**
48
+ * Preview a set-op (union / intersection / difference) across two or more
49
+ * sources, optionally projected into a target list. Same drop-warning
50
+ * semantics as `ComputeDelta`: any operation that would remove records
51
+ * from an existing target sets `Counts.Remove > 0` and produces a
52
+ * `WILL_REMOVE_RECORDS` warning.
53
+ *
54
+ * `difference` is left-to-right: `inputs[0] − inputs[1] − inputs[2] …`.
55
+ */
56
+ ComputeSetOp(op: SetOpKind, inputs: ListSource[], target?: DeltaTarget): Promise<ListDelta>;
57
+ /**
58
+ * Apply a previously previewed `ListDelta` to its target list. Enforces
59
+ * the full drop-row warning contract (server-side, non-bypassable):
60
+ *
61
+ * 1. The token must verify (signature + 5-min TTL).
62
+ * 2. If the delta would remove records, `opts.ConfirmDrops` must be true.
63
+ * 3. The target list's current membership must still equal what the
64
+ * preview observed — otherwise `STALE_DELTA` (UI is expected to
65
+ * re-preview and ask the user again).
66
+ * 4. Caller must hold Editor permission (placeholder — Phase 2 wires
67
+ * the real ResourcePermission check).
68
+ *
69
+ * Only mutates when all four pass. Concurrent mutations between preview
70
+ * and apply surface as `STALE_DELTA` rather than silently overwriting.
71
+ */
72
+ ApplyDelta(delta: ListDelta, opts: {
73
+ ConfirmDrops: boolean;
74
+ DeltaToken: string;
75
+ }): Promise<ApplyResult>;
76
+ /**
77
+ * Materialize a new list from a User View. Creates an `MJ: List` record,
78
+ * captures lineage when requested, and bulk-inserts the resolved members
79
+ * as `MJ: List Details` rows.
80
+ *
81
+ * Lineage semantics:
82
+ * - `RememberLineage = false` → one-shot copy; the list cannot be
83
+ * refreshed against the view later. Useful when the user explicitly
84
+ * wants a frozen point-in-time snapshot decoupled from the view.
85
+ * - `RememberLineage = true, UseSnapshot = false` → list remembers
86
+ * `SourceViewID`; future refreshes re-run the **live** view.
87
+ * - `RememberLineage = true, UseSnapshot = true` → list remembers
88
+ * `SourceViewID` and a JSON snapshot of the view's filter state;
89
+ * future refreshes re-evaluate that **snapshot** even if the view
90
+ * itself has since been edited.
91
+ *
92
+ * Never produces drops (target is brand new) — no delta-token needed.
93
+ */
94
+ MaterializeFromView(viewId: string, opts: MaterializeOptions): Promise<ApplyResult>;
95
+ /**
96
+ * Refresh an existing list against its captured source view. The list
97
+ * must have been created with `RememberLineage = true` (i.e. it has a
98
+ * non-null `SourceViewID`) — otherwise this returns `TARGET_NOT_FOUND`.
99
+ *
100
+ * When `list.UseSnapshot = true`, the source is re-evaluated against the
101
+ * filter snapshot captured at materialization time. When `false`, the
102
+ * live view is re-run — which means edits to the view since
103
+ * materialization will be reflected in the result.
104
+ *
105
+ * In `Sync` mode, callers MUST pass `ConfirmDrops: true` or the
106
+ * server-side drop guard will reject the apply with `DROP_NOT_CONFIRMED`.
107
+ * On success, `LastRefreshedAt` and `LastRefreshedByUserID` are updated.
108
+ */
109
+ RefreshFromSource(listId: string, mode: ListRefreshMode, opts: {
110
+ ConfirmDrops: boolean;
111
+ }): Promise<ApplyResult>;
112
+ /**
113
+ * Add a view's results to an existing list. Always additive — never
114
+ * removes existing members. Dedupes silently, so safe to re-run.
115
+ *
116
+ * No drop-confirmation needed (this op cannot drop). The implementation
117
+ * pipes through `ComputeDelta` + `ApplyDelta` so it still gets the
118
+ * token/staleness/permission machinery for free.
119
+ */
120
+ AddViewResultsToList(viewId: string, listId: string): Promise<ApplyResult>;
121
+ private resolveListSource;
122
+ private resolveViewSource;
123
+ private resolveAdhocSource;
124
+ /**
125
+ * Resolve a list's entity name from its `EntityID` foreign key.
126
+ * Single-entity is intentional — multi-entity lists are not supported.
127
+ */
128
+ private resolveEntityNameFromList;
129
+ /**
130
+ * Serialize a primary-key tuple into the MJ List Detail `RecordID` format.
131
+ * Single-PK entities return the raw value; composite-PK entities use the
132
+ * canonical `Field1|Value1||Field2|Value2` concatenation matching
133
+ * `CompositeKey.ToConcatenatedString` defaults.
134
+ */
135
+ private serializeRecordId;
136
+ private buildDelta;
137
+ private buildWarnings;
138
+ /**
139
+ * Apply a set-op across N already-resolved sources. We accept the resolved
140
+ * sets (not raw `ListSource[]`) so callers can decide how to handle mixed
141
+ * entities before we collapse identities to strings.
142
+ */
143
+ private applySetOp;
144
+ private detectMixedEntities;
145
+ private assertEntitiesMatch;
146
+ private verifyTokenForDelta;
147
+ private verifyTargetNotDrifted;
148
+ /**
149
+ * Placeholder for the Phase 2 ResourcePermission check. Phase 0 ships a
150
+ * stub so the call site exists and the failure mode is reachable in
151
+ * tests, but it currently always passes — server bootstrap MUST wire in
152
+ * the real check before Phase 2 ships.
153
+ */
154
+ private checkEditorPermission;
155
+ /**
156
+ * Idempotent batch apply. We persist add/remove independently so a
157
+ * single bad record (e.g. constraint violation) doesn't abort the whole
158
+ * delta — we collect failures and surface a `PARTIAL_SUCCESS` result.
159
+ */
160
+ private applyDeltaMutations;
161
+ /**
162
+ * Locate and delete the `MJ: List Details` rows that map to the given
163
+ * record IDs. We scope the lookup to a single `ListID` so a bad RecordID
164
+ * value can't accidentally drop rows from other lists.
165
+ */
166
+ private removeDeltaRecords;
167
+ /**
168
+ * Pick the source for a refresh based on the list's lineage. Live mode
169
+ * just points back at the original view ID. Snapshot mode reads the
170
+ * captured filter blob and constructs an ad-hoc source against the
171
+ * list's entity using whichever WHERE clause the view had at
172
+ * materialization time.
173
+ */
174
+ private resolveRefreshSource;
175
+ /**
176
+ * Parse the captured `SourceFilterSnapshot` and turn it into an ad-hoc
177
+ * `ListSource`. We prefer `customWhereClause` over `whereClause` because
178
+ * the custom form is the user-authored override; smart-filter clauses
179
+ * fall in last because they're AI-derived. Returns null if no usable
180
+ * clause is present (in which case the caller surfaces a clear error).
181
+ */
182
+ private buildAdhocSourceFromSnapshot;
183
+ /**
184
+ * Bump `LastRefreshedAt` + `LastRefreshedByUserID` after a successful
185
+ * refresh. Best-effort — a failure here doesn't roll back the apply
186
+ * (the records are already in the right state), but we do log it so
187
+ * the discrepancy is observable.
188
+ */
189
+ private stampRefreshMetadata;
190
+ private failure;
191
+ /**
192
+ * Load the User View and serialize its filter state into a JSON blob.
193
+ * We only capture the fields that actually drive the result set — grid
194
+ * layout, sort, and smart-filter explanation are layout/explanation
195
+ * metadata, not part of the filter contract, so they're omitted. The
196
+ * snapshot is versioned (`v: 1`) so future schema evolutions can
197
+ * branch on shape without breaking older snapshots.
198
+ */
199
+ private captureViewFilterSnapshot;
200
+ /**
201
+ * Create + save the parent `MJ: List` record. Lineage fields are only
202
+ * populated when `RememberLineage = true` on `opts` — otherwise the new
203
+ * list is a plain one-shot copy with no upstream reference.
204
+ */
205
+ private createListWithLineage;
206
+ /**
207
+ * Bulk-insert `MJ: List Details` rows for the given record IDs. We
208
+ * surface a per-record error rather than aborting the whole batch so
209
+ * the caller can show partial-success counts.
210
+ */
211
+ private insertListMembers;
212
+ private metadata;
213
+ private runView;
214
+ }
215
+ //# sourceMappingURL=ListOperations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ListOperations.d.ts","sourceRoot":"","sources":["../src/ListOperations.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,iBAAiB,EAKjB,QAAQ,EACT,MAAM,sBAAsB,CAAC;AAS9B,OAAO,KAAK,EACV,WAAW,EAIX,SAAS,EAGT,eAAe,EACf,UAAU,EACV,kBAAkB,EAClB,iBAAiB,EACjB,SAAS,EACV,MAAM,4BAA4B,CAAC;AAEpC;;yDAEyD;AACzD,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,KAAK,CAAC;AAE7C;;;;;;;;GAQG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAW;IACvC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgC;gBAE7C,WAAW,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,iBAAiB;IAK/D;;;;;;;;OAQG;IACU,aAAa,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAiB1E;;;OAGG;IACU,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAIvE;;;;;;;;;;OAUG;IACU,YAAY,CACvB,MAAM,EAAE,WAAW,EACnB,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,eAAe,GACpB,OAAO,CAAC,SAAS,CAAC;IA4CrB;;;;;;;;OAQG;IACU,YAAY,CACvB,EAAE,EAAE,SAAS,EACb,MAAM,EAAE,UAAU,EAAE,EACpB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,SAAS,CAAC;IA0DrB;;;;;;;;;;;;;;OAcG;IACU,UAAU,CACrB,KAAK,EAAE,SAAS,EAChB,IAAI,EAAE;QAAE,YAAY,EAAE,OAAO,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,GAClD,OAAO,CAAC,WAAW,CAAC;IA6BvB;;;;;;;;;;;;;;;;;OAiBG;IACU,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,WAAW,CAAC;IA0ChG;;;;;;;;;;;;;OAaG;IACU,iBAAiB,CAC5B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,eAAe,EACrB,IAAI,EAAE;QAAE,YAAY,EAAE,OAAO,CAAA;KAAE,GAC9B,OAAO,CAAC,WAAW,CAAC;IAoBvB;;;;;;;OAOG;IACU,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;YAWzE,iBAAiB;YA6BjB,iBAAiB;YAsCjB,kBAAkB;IA0BhC;;;OAGG;IACH,OAAO,CAAC,yBAAyB;IAWjC;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB;YAaX,UAAU;IAwCxB,OAAO,CAAC,aAAa;IAmCrB;;;;OAIG;IACH,OAAO,CAAC,UAAU;IA0BlB,OAAO,CAAC,mBAAmB;IAY3B,OAAO,CAAC,mBAAmB;YAQb,mBAAmB;YAoCnB,sBAAsB;IAwBpC;;;;;OAKG;YACW,qBAAqB;IAOnC;;;;OAIG;YACW,mBAAmB;IAkDjC;;;;OAIG;YACW,kBAAkB;IA0BhC;;;;;;OAMG;YACW,oBAAoB;IAqClC;;;;;;OAMG;IACH,OAAO,CAAC,4BAA4B;IA0BpC;;;;;OAKG;YACW,oBAAoB;IAkBlC,OAAO,CAAC,OAAO;IAIf;;;;;;;OAOG;YACW,yBAAyB;IAwBvC;;;;OAIG;YACW,qBAAqB;IA+BnC;;;;OAIG;YACW,iBAAiB;IA4B/B,OAAO,CAAC,QAAQ;IAQhB,OAAO,CAAC,OAAO;CAGhB"}