@classytic/party 0.1.0 → 0.2.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/CHANGELOG.md +59 -0
- package/LICENSE +1 -1
- package/README.md +29 -4
- package/dist/events.d.ts +3 -8
- package/dist/events.js +21 -31
- package/dist/index.d.ts +253 -90
- package/dist/index.js +641 -329
- package/dist/maintenance.d.ts +165 -0
- package/dist/maintenance.js +353 -0
- package/package.json +15 -8
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,65 @@
|
|
|
3
3
|
All notable changes to `@classytic/party` are documented here. Adheres to
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/) and [SemVer](https://semver.org/).
|
|
5
5
|
|
|
6
|
+
## [0.2.0] — 2026-08-17
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- `defineParty(shape)` / `blueprint.bind(connection, runtime)` — describe/bind construction API replacing `createParty`; describe phase is pure and safe at import time
|
|
10
|
+
- `SubjectRef` / `PARTY_MODEL_NAME` exported from `./domain/subject` — canonical polymorphic subject seam for cross-kernel refs (`@classytic/access`, `@classytic/muster`, etc.)
|
|
11
|
+
- `src/maintenance/phone-identity-repair.ts` — repair utility for phone-identity drift
|
|
12
|
+
|
|
13
|
+
### Removed
|
|
14
|
+
- `createParty` (replaced by `defineParty`)
|
|
15
|
+
|
|
16
|
+
## 1.0.0 - 2026-08-07 (unpublished)
|
|
17
|
+
|
|
18
|
+
### Requirements
|
|
19
|
+
|
|
20
|
+
- **New peer dependency: `@classytic/contact >=0.1.0`** — install it alongside
|
|
21
|
+
this package. It owns contact-point canonicalisation (E.164 phones, emails),
|
|
22
|
+
which `resolveOrCreate` / `syncIdentity` derive their `identityKeys` from.
|
|
23
|
+
A PEER rather than a regular dependency on purpose: npm may nest a regular one
|
|
24
|
+
under `party/node_modules`, giving the host a second module instance, and then
|
|
25
|
+
`err instanceof PhoneFormatError` is `false` at a host catch site that imported
|
|
26
|
+
its own copy — a guard that compiles, runs, and matches nothing.
|
|
27
|
+
|
|
28
|
+
### Breaking
|
|
29
|
+
|
|
30
|
+
- **Contact input that cannot be canonicalised now THROWS** instead of becoming
|
|
31
|
+
an identity key. A phone that is not a valid, dialable number raises
|
|
32
|
+
`PartyPhoneFormatError` (400 / `party.phone_invalid`); an unparseable email
|
|
33
|
+
raises `EmailFormatError`. `0.1.0` degraded both — a rejected phone became
|
|
34
|
+
`+${digits}`, producing keys like `phone:+01309000993` that are neither a
|
|
35
|
+
national number nor E.164, so two spellings of one person keyed differently and
|
|
36
|
+
the identity forked silently. There is no lenient variant.
|
|
37
|
+
- **Phone canonicalisation requires a region for national-format input.**
|
|
38
|
+
`PartyShape.defaultPhoneRegion` (ISO 3166-1 alpha-2, supplied by the
|
|
39
|
+
deployment — this kernel names no country). Unset ⇒ E.164-only input is
|
|
40
|
+
accepted and a national format is refused rather than guessed at. An explicit
|
|
41
|
+
`+…` number is never re-read through the region.
|
|
42
|
+
- **`ContactPoint.value` stores the canonical E.164**, with the caller's original
|
|
43
|
+
spelling preserved as `ContactPoint.input` (never keyed, never matched on).
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
|
|
47
|
+
- Added `PartyShape.identityContactTypes` (`email | phone`, both by default).
|
|
48
|
+
A host can exclude shared contact channels from automatic identity resolution
|
|
49
|
+
while retaining normalized contact points. Tax identifiers and external
|
|
50
|
+
references remain authoritative. Existing defaults are unchanged.
|
|
51
|
+
- The unique `identityKeys` index now explicitly includes only non-empty arrays.
|
|
52
|
+
This permits multiple name-only or contact-only parties when contact channels
|
|
53
|
+
are non-authoritative, without weakening uniqueness for real identity keys.
|
|
54
|
+
- Canonical email output is unchanged for every valid address — trim plus
|
|
55
|
+
lowercase, and still **no** Gmail-dot or `+tag` folding, since folding a
|
|
56
|
+
provider's rule merges two distinct inboxes into one identity. No existing key
|
|
57
|
+
is re-derived.
|
|
58
|
+
- `libphonenumber-js` is no longer a direct dependency.
|
|
59
|
+
|
|
60
|
+
## 0.1.1 - 2026-07-22 (unpublished)
|
|
61
|
+
|
|
62
|
+
### Changed
|
|
63
|
+
- Internal: `InProcess*Bus` is now a thin subclass of `@classytic/primitives/event-infra`'s shared `InProcessEventBus` (dedup / error-isolation / close semantics unchanged); `MemoryOutboxStore` re-exported from the shared runtime where applicable. No public API, event-name, or wire-envelope change. Requires `@classytic/primitives >=0.13.0`.
|
|
64
|
+
|
|
6
65
|
## [0.1.0] - 2026-07-13
|
|
7
66
|
|
|
8
67
|
Initial release — the Party / Business-Partner identity kernel.
|
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -48,12 +48,12 @@ Peers: `@classytic/mongokit >=3.20`, `@classytic/primitives >=0.9.1`,
|
|
|
48
48
|
|
|
49
49
|
```ts
|
|
50
50
|
import mongoose from 'mongoose';
|
|
51
|
-
import {
|
|
51
|
+
import { defineParty } from '@classytic/party';
|
|
52
52
|
|
|
53
|
-
const party =
|
|
54
|
-
connection: mongoose.connection,
|
|
53
|
+
const party = defineParty({
|
|
55
54
|
multiTenant: false, // identity is company-wide here; pass true for per-tenant SaaS
|
|
56
|
-
|
|
55
|
+
defaultPhoneRegion: 'BD',
|
|
56
|
+
}).bind(mongoose.connection);
|
|
57
57
|
|
|
58
58
|
// Resolve-or-create by ANY identity signal — race-safe, dedup by email/phone/vat.
|
|
59
59
|
const { party: ada, created } = await party.port.resolveOrCreate(
|
|
@@ -76,6 +76,27 @@ const snap = await party.port.snapshot(String(ada._id), {});
|
|
|
76
76
|
await party.repositories.party.merge(dupId, survivorId, {});
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
+
### Contacts are not always proof of identity
|
|
80
|
+
|
|
81
|
+
Email and phone are authoritative identity keys by default, preserving the
|
|
82
|
+
race-safe behavior existing commerce hosts rely on. Domains where people often
|
|
83
|
+
share contact details can keep those values as contact data without allowing
|
|
84
|
+
them to collapse two parties:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
const party = defineParty({
|
|
88
|
+
multiTenant: true,
|
|
89
|
+
defaultPhoneRegion: 'GB',
|
|
90
|
+
identityContactTypes: [],
|
|
91
|
+
}).bind(mongoose.connection);
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Tax identifiers and explicit external references remain authoritative. A
|
|
95
|
+
domain-specific facet (for example a legal Client) can perform candidate-match
|
|
96
|
+
review and then attach its stable external reference or explicitly merge the
|
|
97
|
+
confirmed duplicate. This setting shapes persisted `identityKeys`; migrate
|
|
98
|
+
existing rows before changing it on a live deployment.
|
|
99
|
+
|
|
79
100
|
## The `PartyPort` — the reusable contract
|
|
80
101
|
|
|
81
102
|
Consuming domains depend on `engine.port` (a `PartyPort`), not the models:
|
|
@@ -111,3 +132,7 @@ entities — that's the duplication this kernel exists to remove.
|
|
|
111
132
|
## License
|
|
112
133
|
|
|
113
134
|
MIT
|
|
135
|
+
|
|
136
|
+
## Trademark
|
|
137
|
+
|
|
138
|
+
Code is MIT-licensed. "Classytic"/"arc" names + logos are trademarks of Classytic LLC — see [TRADEMARK.md](TRADEMARK.md).
|
package/dist/events.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { DomainEvent, EventHandler, EventMeta, EventTransport } from "@classytic/primitives/events";
|
|
3
|
+
import { InProcessEventBus } from "@classytic/primitives/event-infra";
|
|
3
4
|
import { Types } from "mongoose";
|
|
4
5
|
import { OperationContext } from "@classytic/primitives/context";
|
|
5
6
|
import "@classytic/primitives/reference";
|
|
@@ -192,16 +193,10 @@ interface PartyLogger {
|
|
|
192
193
|
declare function createEvent<T>(type: string, payload: T, ctx?: PartyContext, meta?: Partial<EventMeta>): DomainEvent<T>;
|
|
193
194
|
//#endregion
|
|
194
195
|
//#region src/events/in-process-bus.d.ts
|
|
195
|
-
declare class InProcessPartyBus
|
|
196
|
-
readonly name = "in-process-party";
|
|
197
|
-
private subs;
|
|
198
|
-
private readonly logger;
|
|
196
|
+
declare class InProcessPartyBus extends InProcessEventBus {
|
|
199
197
|
constructor(options?: {
|
|
200
|
-
logger?: PartyLogger
|
|
198
|
+
logger?: PartyLogger;
|
|
201
199
|
});
|
|
202
|
-
publish(event: DomainEvent): Promise<void>;
|
|
203
|
-
subscribe(pattern: string, handler: EventHandler): Promise<() => void>;
|
|
204
|
-
close(): Promise<void>;
|
|
205
200
|
}
|
|
206
201
|
//#endregion
|
|
207
202
|
//#region src/events/dispatch.d.ts
|
package/dist/events.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { createEvent as createEvent$1
|
|
2
|
+
import { createEvent as createEvent$1 } from "@classytic/primitives/events";
|
|
3
|
+
import { InProcessEventBus } from "@classytic/primitives/event-infra";
|
|
3
4
|
//#region src/events/event-constants.ts
|
|
4
5
|
/** PACKAGE_RULES §12: `package:resource.verb`. Emit ONLY these catalog names. */
|
|
5
6
|
const PARTY_EVENTS = {
|
|
@@ -158,37 +159,24 @@ function createEvent(type, payload, ctx, meta) {
|
|
|
158
159
|
//#region src/events/in-process-bus.ts
|
|
159
160
|
/**
|
|
160
161
|
* In-process event bus — default fallback when no `eventTransport` is given to
|
|
161
|
-
* `createParty`.
|
|
162
|
-
*
|
|
163
|
-
*
|
|
162
|
+
* `createParty`.
|
|
163
|
+
*
|
|
164
|
+
* A thin subclass of `@classytic/primitives/event-infra`'s shared
|
|
165
|
+
* `InProcessEventBus`. Prior semantics preserved: glob matching via
|
|
166
|
+
* `matchEventPattern`, per-handler error isolation, idempotent `close()`.
|
|
167
|
+
* Party historically SWALLOWED handler errors when no logger was supplied —
|
|
168
|
+
* that default is preserved by passing `logger: null` unless the host injects
|
|
169
|
+
* a `PartyLogger`, whose structured `error(msg, meta)` slot is bridged onto
|
|
170
|
+
* the shared error-logger contract.
|
|
164
171
|
*/
|
|
165
|
-
var InProcessPartyBus = class {
|
|
166
|
-
name = "in-process-party";
|
|
167
|
-
subs = [];
|
|
168
|
-
logger;
|
|
172
|
+
var InProcessPartyBus = class extends InProcessEventBus {
|
|
169
173
|
constructor(options) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
} catch (err) {
|
|
177
|
-
this.logger?.error?.(`[party] event handler error [${event.type}]`, { error: err instanceof Error ? err.message : String(err) });
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
async subscribe(pattern, handler) {
|
|
181
|
-
const sub = {
|
|
182
|
-
pattern,
|
|
183
|
-
handler
|
|
184
|
-
};
|
|
185
|
-
this.subs.push(sub);
|
|
186
|
-
return () => {
|
|
187
|
-
this.subs = this.subs.filter((s) => s !== sub);
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
async close() {
|
|
191
|
-
this.subs = [];
|
|
174
|
+
const partyLogger = options?.logger;
|
|
175
|
+
super({
|
|
176
|
+
name: "in-process-party",
|
|
177
|
+
logLabel: "party",
|
|
178
|
+
logger: partyLogger?.error ? { error: (message, ...args) => partyLogger.error?.(message, { args }) } : null
|
|
179
|
+
});
|
|
192
180
|
}
|
|
193
181
|
};
|
|
194
182
|
//#endregion
|
|
@@ -211,7 +199,9 @@ var PartyEventDispatcher = class {
|
|
|
211
199
|
return this.transport.publish(event);
|
|
212
200
|
}
|
|
213
201
|
subscribe(pattern, handler) {
|
|
214
|
-
|
|
202
|
+
const subscribe = this.transport.subscribe?.bind(this.transport);
|
|
203
|
+
if (!subscribe) throw new Error("party: configured event transport does not support subscribe");
|
|
204
|
+
return subscribe(pattern, handler);
|
|
215
205
|
}
|
|
216
206
|
/**
|
|
217
207
|
* Route a domain event with full §P8 discipline. Reads the pending queue and
|