@fonderie/customers 1.1.0 → 1.1.2
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/brain/outcomes.md +189 -0
- package/brain/signatures.md +244 -0
- package/dist/migrations/sql/001_customers.sql +111 -0
- package/dist/migrations/sql/002_customers_sex.sql +2 -0
- package/dist/migrations/sql/003_customer_sequences.sql +6 -0
- package/dist/migrations/sql/004_customer_sequences_per_workspace.sql +1 -0
- package/dist/migrations/sql/005_drop_job_title.sql +1 -0
- package/dist/migrations/sql/006_unique_email_phone.sql +23 -0
- package/dist/migrations/sql/007_rename_archived_to_blacklisted.sql +13 -0
- package/dist/migrations/sql/008_blacklist_reason.sql +2 -0
- package/dist/migrations/sql/009_customer_relationships.sql +25 -0
- package/dist/migrations/sql/010_customer_labels.sql +75 -0
- package/dist/migrations/sql/011_label_fk_set_null.sql +17 -0
- package/package.json +5 -4
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
<!-- GENERATED — do not edit. Regenerate with: npm run docs:signatures -->
|
|
2
|
+
|
|
3
|
+
# @fonderie/customers — outcomes
|
|
4
|
+
|
|
5
|
+
What this package does to a running app: tables its migrations create,
|
|
6
|
+
rows it seeds, routes it registers. Generated from the migration SQL and
|
|
7
|
+
route tables in source — trust this file instead of reading `dist/` or
|
|
8
|
+
downloading tarballs.
|
|
9
|
+
|
|
10
|
+
## Database tables (after all migrations)
|
|
11
|
+
|
|
12
|
+
### `fonderie_addresses`
|
|
13
|
+
|
|
14
|
+
```sql
|
|
15
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid()
|
|
16
|
+
country_iso TEXT NOT NULL
|
|
17
|
+
subdivision1_iso TEXT
|
|
18
|
+
subdivision2_iso TEXT
|
|
19
|
+
zip_postal_code TEXT NOT NULL
|
|
20
|
+
line1 TEXT
|
|
21
|
+
line2 TEXT
|
|
22
|
+
unit TEXT
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### `fonderie_customer_addresses`
|
|
26
|
+
|
|
27
|
+
```sql
|
|
28
|
+
addr_id UUID NOT NULL REFERENCES fonderie_addresses(id) ON DELETE CASCADE
|
|
29
|
+
customer_id UUID NOT NULL REFERENCES fonderie_customers(id) ON DELETE CASCADE
|
|
30
|
+
label TEXT NOT NULL DEFAULT 'service'
|
|
31
|
+
is_primary BOOLEAN NOT NULL DEFAULT false
|
|
32
|
+
label_id UUID REFERENCES fonderie_customer_labels(id)
|
|
33
|
+
CONSTRAINT fonderie_customer_addresses_label_id_fkey FOREIGN KEY (label_id) REFERENCES fonderie_customer_labels(id) ON DELETE SET NULL
|
|
34
|
+
-- PRIMARY KEY (addr_id, customer_id)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### `fonderie_customer_emails`
|
|
38
|
+
|
|
39
|
+
```sql
|
|
40
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid()
|
|
41
|
+
customer_id UUID NOT NULL REFERENCES fonderie_customers(id) ON DELETE CASCADE
|
|
42
|
+
email TEXT NOT NULL
|
|
43
|
+
label TEXT NOT NULL DEFAULT 'work'
|
|
44
|
+
is_primary BOOLEAN NOT NULL DEFAULT false
|
|
45
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
46
|
+
label_id UUID REFERENCES fonderie_customer_labels(id)
|
|
47
|
+
CONSTRAINT fonderie_customer_emails_label_id_fkey FOREIGN KEY (label_id) REFERENCES fonderie_customer_labels(id) ON DELETE SET NULL
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### `fonderie_customer_labels`
|
|
51
|
+
|
|
52
|
+
```sql
|
|
53
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid()
|
|
54
|
+
type fonderie_customer_label_type NOT NULL
|
|
55
|
+
value TEXT NOT NULL
|
|
56
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
57
|
+
-- CONSTRAINT uq_fcl_type_value UNIQUE (type, value)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### `fonderie_customer_notes`
|
|
61
|
+
|
|
62
|
+
```sql
|
|
63
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid()
|
|
64
|
+
customer_id UUID NOT NULL REFERENCES fonderie_customers(id) ON DELETE CASCADE
|
|
65
|
+
author_id UUID
|
|
66
|
+
body TEXT NOT NULL
|
|
67
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
68
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### `fonderie_customer_phones`
|
|
72
|
+
|
|
73
|
+
```sql
|
|
74
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid()
|
|
75
|
+
customer_id UUID NOT NULL REFERENCES fonderie_customers(id) ON DELETE CASCADE
|
|
76
|
+
phone TEXT NOT NULL
|
|
77
|
+
label TEXT NOT NULL DEFAULT 'mobile'
|
|
78
|
+
is_primary BOOLEAN NOT NULL DEFAULT false
|
|
79
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
80
|
+
label_id UUID REFERENCES fonderie_customer_labels(id)
|
|
81
|
+
CONSTRAINT fonderie_customer_phones_label_id_fkey FOREIGN KEY (label_id) REFERENCES fonderie_customer_labels(id) ON DELETE SET NULL
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### `fonderie_customer_relationships`
|
|
85
|
+
|
|
86
|
+
```sql
|
|
87
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid()
|
|
88
|
+
workspace_id UUID NOT NULL
|
|
89
|
+
customer_id UUID NOT NULL REFERENCES fonderie_customers(id) ON DELETE CASCADE
|
|
90
|
+
related_id UUID NOT NULL REFERENCES fonderie_customers(id) ON DELETE CASCADE
|
|
91
|
+
relationship TEXT NOT NULL
|
|
92
|
+
is_primary BOOLEAN NOT NULL DEFAULT false
|
|
93
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
94
|
+
-- UNIQUE (customer_id, related_id)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### `fonderie_customer_sequences`
|
|
98
|
+
|
|
99
|
+
```sql
|
|
100
|
+
workspace_id UUID NOT NULL
|
|
101
|
+
prefix TEXT NOT NULL
|
|
102
|
+
next_val BIGINT NOT NULL DEFAULT 1
|
|
103
|
+
-- PRIMARY KEY (workspace_id, prefix)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### `fonderie_customer_tags`
|
|
107
|
+
|
|
108
|
+
```sql
|
|
109
|
+
customer_id UUID NOT NULL REFERENCES fonderie_customers(id) ON DELETE CASCADE
|
|
110
|
+
tag TEXT NOT NULL
|
|
111
|
+
-- PRIMARY KEY (customer_id, tag)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### `fonderie_customers`
|
|
115
|
+
|
|
116
|
+
```sql
|
|
117
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid()
|
|
118
|
+
workspace_id UUID NOT NULL
|
|
119
|
+
type TEXT NOT NULL DEFAULT 'individual'
|
|
120
|
+
first_name TEXT
|
|
121
|
+
last_name TEXT
|
|
122
|
+
company_name TEXT
|
|
123
|
+
avatar_url TEXT
|
|
124
|
+
locale TEXT NOT NULL DEFAULT 'en-US'
|
|
125
|
+
reference_code TEXT
|
|
126
|
+
is_blacklisted BOOLEAN NOT NULL DEFAULT false
|
|
127
|
+
created_by UUID
|
|
128
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
129
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
130
|
+
sex TEXT NOT NULL DEFAULT 'UNKNOWN'
|
|
131
|
+
blacklist_reason TEXT
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Raw SQL ships in `node_modules/@fonderie/customers/dist/migrations/sql/` — read it there if you must; never download tarballs.
|
|
135
|
+
|
|
136
|
+
## Seeded rows (behavioral contract)
|
|
137
|
+
|
|
138
|
+
```sql
|
|
139
|
+
INSERT INTO fonderie_customer_labels (type, value) VALUES ('email', 'work'), ('email', 'personal'), ('email', 'billing'), ('phone', 'mobile'), ('phone', 'office'), ('phone', 'home'), ('phone', 'fax'), ('address', 'service'), ('address', 'billing'), ('address', 'other') ON CONFLICT (type, value) DO NOTHING;
|
|
140
|
+
INSERT INTO fonderie_customer_labels (type, value) SELECT DISTINCT 'email'::fonderie_customer_label_type, label FROM fonderie_customer_emails WHERE label IS NOT NULL ON CONFLICT (type, value) DO NOTHING;
|
|
141
|
+
INSERT INTO fonderie_customer_labels (type, value) SELECT DISTINCT 'phone'::fonderie_customer_label_type, label FROM fonderie_customer_phones WHERE label IS NOT NULL ON CONFLICT (type, value) DO NOTHING;
|
|
142
|
+
INSERT INTO fonderie_customer_labels (type, value) SELECT DISTINCT 'address'::fonderie_customer_label_type, label FROM fonderie_customer_addresses WHERE label IS NOT NULL ON CONFLICT (type, value) DO NOTHING;
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## HTTP routes registered
|
|
146
|
+
|
|
147
|
+
| Method | Path | Middleware chain (auth / validation / handler) |
|
|
148
|
+
|---|---|---|
|
|
149
|
+
| GET | `/customers` | `requireAuth → wsCtx → customer.list` |
|
|
150
|
+
| POST | `/customers` | `requireAuth → wsCtx → validate(createCustomerSchema) → customer.create` |
|
|
151
|
+
| DELETE | `/customers/:customerId` | `requireAuth → wsCtx → customer.delete` |
|
|
152
|
+
| GET | `/customers/:customerId` | `requireAuth → wsCtx → customer.get` |
|
|
153
|
+
| PUT | `/customers/:customerId` | `requireAuth → wsCtx → validate(updateCustomerSchema) → customer.update` |
|
|
154
|
+
| GET | `/customers/:customerId/addresses` | `requireAuth → wsCtx → address.list` |
|
|
155
|
+
| POST | `/customers/:customerId/addresses` | `requireAuth → wsCtx → validate(addAddressSchema) → address.add` |
|
|
156
|
+
| DELETE | `/customers/:customerId/addresses/:addrId` | `requireAuth → wsCtx → address.remove` |
|
|
157
|
+
| PATCH | `/customers/:customerId/addresses/:addrId` | `requireAuth → wsCtx → validate(updateAddressSchema) → address.update` |
|
|
158
|
+
| PUT | `/customers/:customerId/addresses/:addrId/primary` | `requireAuth → wsCtx → address.setPrimary` |
|
|
159
|
+
| POST | `/customers/:customerId/blacklist` | `requireAuth → wsCtx → validate(blacklistSchema) → customer.blacklist` |
|
|
160
|
+
| GET | `/customers/:customerId/emails` | `requireAuth → wsCtx → email.list` |
|
|
161
|
+
| POST | `/customers/:customerId/emails` | `requireAuth → wsCtx → validate(addEmailSchema) → email.add` |
|
|
162
|
+
| DELETE | `/customers/:customerId/emails/:emailId` | `requireAuth → wsCtx → email.remove` |
|
|
163
|
+
| PATCH | `/customers/:customerId/emails/:emailId` | `requireAuth → wsCtx → validate(updateEmailSchema) → email.update` |
|
|
164
|
+
| PUT | `/customers/:customerId/emails/:emailId/primary` | `requireAuth → wsCtx → email.setPrimary` |
|
|
165
|
+
| GET | `/customers/:customerId/notes` | `requireAuth → wsCtx → note.list` |
|
|
166
|
+
| POST | `/customers/:customerId/notes` | `requireAuth → wsCtx → validate(noteSchema) → note.create` |
|
|
167
|
+
| DELETE | `/customers/:customerId/notes/:noteId` | `requireAuth → wsCtx → note.delete` |
|
|
168
|
+
| PUT | `/customers/:customerId/notes/:noteId` | `requireAuth → wsCtx → validate(noteSchema) → note.update` |
|
|
169
|
+
| GET | `/customers/:customerId/phones` | `requireAuth → wsCtx → phone.list` |
|
|
170
|
+
| POST | `/customers/:customerId/phones` | `requireAuth → wsCtx → validate(addPhoneSchema) → phone.add` |
|
|
171
|
+
| DELETE | `/customers/:customerId/phones/:phoneId` | `requireAuth → wsCtx → phone.remove` |
|
|
172
|
+
| PATCH | `/customers/:customerId/phones/:phoneId` | `requireAuth → wsCtx → validate(updatePhoneSchema) → phone.update` |
|
|
173
|
+
| PUT | `/customers/:customerId/phones/:phoneId/primary` | `requireAuth → wsCtx → phone.setPrimary` |
|
|
174
|
+
| GET | `/customers/:customerId/relationships` | `requireAuth → wsCtx → relationship.list` |
|
|
175
|
+
| POST | `/customers/:customerId/relationships` | `requireAuth → wsCtx → validate(addRelationshipSchema) → relationship.add` |
|
|
176
|
+
| DELETE | `/customers/:customerId/relationships/:relatedId` | `requireAuth → wsCtx → relationship.remove` |
|
|
177
|
+
| PUT | `/customers/:customerId/relationships/:relatedId/primary` | `requireAuth → wsCtx → relationship.setPrimary` |
|
|
178
|
+
| GET | `/customers/:customerId/tags` | `requireAuth → wsCtx → tag.list` |
|
|
179
|
+
| POST | `/customers/:customerId/tags` | `requireAuth → wsCtx → validate(addTagSchema) → tag.add` |
|
|
180
|
+
| DELETE | `/customers/:customerId/tags/:tag` | `requireAuth → wsCtx → tag.remove` |
|
|
181
|
+
| POST | `/customers/:customerId/unblacklist` | `requireAuth → wsCtx → customer.unblacklist` |
|
|
182
|
+
| GET | `/customers/labels` | `requireAuth → wsCtx → label.list` |
|
|
183
|
+
| DELETE | `/customers/labels/:labelId` | `requireAuth → wsCtx → label.remove` |
|
|
184
|
+
|
|
185
|
+
## Migration statements not replayed (verify in raw SQL)
|
|
186
|
+
|
|
187
|
+
- `END IF`
|
|
188
|
+
- `END $$`
|
|
189
|
+
- `CREATE TYPE fonderie_customer_label_type AS ENUM ('email', 'phone', 'address')`
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
<!-- GENERATED — do not edit. Regenerate with: npm run docs:signatures -->
|
|
2
|
+
|
|
3
|
+
# @fonderie/customers — signatures
|
|
4
|
+
|
|
5
|
+
## @fonderie/customers
|
|
6
|
+
|
|
7
|
+
Subpath exports: `@fonderie/customers/types`, `@fonderie/customers/migrations`
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
type CustomersEventKey = (typeof EVENT_KEYS)[keyof typeof EVENT_KEYS];
|
|
11
|
+
|
|
12
|
+
type ICustomersConfig = {
|
|
13
|
+
referenceCodePrefix?: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const EVENT_KEYS: { readonly customerCreated: "fonderie.customer.created"; readonly customerUpdated: "fonderie.customer.updated"; readonly customerDeleted: "fonderie.customer.deleted"; readonly customerBlacklisted: "fonderie.customer.blacklisted"; readonly customerUnblacklisted: "fonderie.customer.unblacklisted"; }
|
|
17
|
+
|
|
18
|
+
interface IAddressDTO {
|
|
19
|
+
countryIso: string;
|
|
20
|
+
subdivision1Iso: string;
|
|
21
|
+
subdivision2Iso: string;
|
|
22
|
+
zipPostalCode: string;
|
|
23
|
+
unit: string;
|
|
24
|
+
line1: string;
|
|
25
|
+
line2: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface ICustomerAddressDTO {
|
|
29
|
+
id: string;
|
|
30
|
+
label: string;
|
|
31
|
+
isPrimary: boolean;
|
|
32
|
+
address: IAddressDTO;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface ICustomerDetailDTO extends ICustomerDTO {
|
|
36
|
+
emails: ICustomerEmailDTO[];
|
|
37
|
+
phones: ICustomerPhoneDTO[];
|
|
38
|
+
addresses: ICustomerAddressDTO[];
|
|
39
|
+
notes: ICustomerNoteDTO[];
|
|
40
|
+
relationships: ICustomerRelationshipExpandedDTO[];
|
|
41
|
+
tags: string[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface ICustomerDTO {
|
|
45
|
+
id: string;
|
|
46
|
+
type: string;
|
|
47
|
+
sex: CustomerSex;
|
|
48
|
+
firstName: string;
|
|
49
|
+
lastName: string;
|
|
50
|
+
companyName: string;
|
|
51
|
+
avatarUrl: string;
|
|
52
|
+
locale: string;
|
|
53
|
+
referenceCode: string;
|
|
54
|
+
blacklisted: {
|
|
55
|
+
status: boolean;
|
|
56
|
+
reason: string | null;
|
|
57
|
+
};
|
|
58
|
+
createdBy: string;
|
|
59
|
+
createdAt: string;
|
|
60
|
+
updatedAt: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface ICustomerEmailDTO {
|
|
64
|
+
id: string;
|
|
65
|
+
email: string;
|
|
66
|
+
label: string;
|
|
67
|
+
isPrimary: boolean;
|
|
68
|
+
createdAt: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface ICustomerNoteDTO {
|
|
72
|
+
id: string;
|
|
73
|
+
authorId: string;
|
|
74
|
+
body: string;
|
|
75
|
+
createdAt: string;
|
|
76
|
+
updatedAt: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
interface ICustomerPhoneDTO {
|
|
80
|
+
id: string;
|
|
81
|
+
phone: string;
|
|
82
|
+
label: string;
|
|
83
|
+
isPrimary: boolean;
|
|
84
|
+
createdAt: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
interface ICustomerTagDTO {
|
|
88
|
+
tag: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function toAddressDTO(a: IAddress): IAddressDTO
|
|
92
|
+
|
|
93
|
+
function toCustomerAddressDTO(ca: ICustomerAddress): ICustomerAddressDTO
|
|
94
|
+
|
|
95
|
+
function toCustomerDetailDTO(c: ICustomerDetail): ICustomerDetailDTO
|
|
96
|
+
|
|
97
|
+
function toCustomerDTO(c: ICustomer): ICustomerDTO
|
|
98
|
+
|
|
99
|
+
function toCustomerEmailDTO(e: ICustomerEmail): ICustomerEmailDTO
|
|
100
|
+
|
|
101
|
+
function toCustomerNoteDTO(n: ICustomerNote): ICustomerNoteDTO
|
|
102
|
+
|
|
103
|
+
function toCustomerPhoneDTO(p: ICustomerPhone): ICustomerPhoneDTO
|
|
104
|
+
|
|
105
|
+
function toCustomerTagDTO(t: ICustomerTag): ICustomerTagDTO
|
|
106
|
+
|
|
107
|
+
new CustomerAddressModel(store: IStoreAdapter): CustomerAddressModel
|
|
108
|
+
.list(customerId: string): Promise<ICustomerAddress[]>
|
|
109
|
+
.add(opts: { customerId: string; countryIso: string; subdivision1Iso?: string | null; subdivision2Iso?: string | null; zipPostalCode: string; unit?: string | null; line1?: string | null; line2?: string | null; labelId: string; isPrimary?: boolean; }): Promise<...>
|
|
110
|
+
.updateLabel(addrId: string, customerId: string, labelId: string): Promise<ICustomerAddress>
|
|
111
|
+
.setPrimary(addrId: string, customerId: string): Promise<void>
|
|
112
|
+
.remove(addrId: string, customerId: string): Promise<void>
|
|
113
|
+
|
|
114
|
+
new CustomerEmailModel(store: IStoreAdapter): CustomerEmailModel
|
|
115
|
+
.list(customerId: string): Promise<ICustomerEmail[]>
|
|
116
|
+
.add(opts: { customerId: string; email: string; labelId: string; isPrimary?: boolean; }): Promise<ICustomerEmail>
|
|
117
|
+
.updateLabel(emailId: string, customerId: string, labelId: string): Promise<ICustomerEmail>
|
|
118
|
+
.setPrimary(emailId: string, customerId: string): Promise<void>
|
|
119
|
+
.remove(emailId: string, customerId: string): Promise<void>
|
|
120
|
+
|
|
121
|
+
new CustomerModel(store: IStoreAdapter): CustomerModel
|
|
122
|
+
.list(opts: ListCustomersOpts): Promise<ICustomer[]>
|
|
123
|
+
.findById(id: string, workspaceId: string): Promise<ICustomer | null>
|
|
124
|
+
.findDetail(id: string, workspaceId: string, depth: 2): Promise<ICustomerDetailD2 | null>
|
|
125
|
+
.create(opts: CreateCustomerOpts): Promise<ICustomer>
|
|
126
|
+
.update(id: string, workspaceId: string, opts: UpdateCustomerOpts, referenceCodePrefix?: string): Promise<ICustomer | null>
|
|
127
|
+
.delete(id: string, workspaceId: string): Promise<void>
|
|
128
|
+
.blacklist(id: string, workspaceId: string, reason?: string | null | undefined): Promise<void>
|
|
129
|
+
.unblacklist(id: string, workspaceId: string): Promise<void>
|
|
130
|
+
|
|
131
|
+
new CustomerNoteModel(store: IStoreAdapter): CustomerNoteModel
|
|
132
|
+
.list(customerId: string): Promise<ICustomerNote[]>
|
|
133
|
+
.create(opts: { customerId: string; authorId?: string | null; body: string; }): Promise<ICustomerNote>
|
|
134
|
+
.update(noteId: string, customerId: string, body: string): Promise<ICustomerNote | null>
|
|
135
|
+
.delete(noteId: string, customerId: string): Promise<void>
|
|
136
|
+
|
|
137
|
+
new CustomerPhoneModel(store: IStoreAdapter): CustomerPhoneModel
|
|
138
|
+
.list(customerId: string): Promise<ICustomerPhone[]>
|
|
139
|
+
.add(opts: { customerId: string; phone: string; labelId: string; isPrimary?: boolean; }): Promise<ICustomerPhone>
|
|
140
|
+
.updateLabel(phoneId: string, customerId: string, labelId: string): Promise<ICustomerPhone>
|
|
141
|
+
.setPrimary(phoneId: string, customerId: string): Promise<void>
|
|
142
|
+
.remove(phoneId: string, customerId: string): Promise<void>
|
|
143
|
+
|
|
144
|
+
new CustomerTagModel(store: IStoreAdapter): CustomerTagModel
|
|
145
|
+
.list(customerId: string): Promise<string[]>
|
|
146
|
+
.add(customerId: string, tag: string): Promise<void>
|
|
147
|
+
.remove(customerId: string, tag: string): Promise<void>
|
|
148
|
+
|
|
149
|
+
new CustomersModule(store: IStoreAdapter, config?: ICustomersConfig, bus?: EventBus | undefined): CustomersModule
|
|
150
|
+
.name: "@fonderie/customers"
|
|
151
|
+
.deps: string[]
|
|
152
|
+
.install(app: IFonderieApp): void
|
|
153
|
+
|
|
154
|
+
type AddressLabel = 'service' | 'billing' | 'other';
|
|
155
|
+
|
|
156
|
+
type CustomerType = 'individual' | 'business';
|
|
157
|
+
|
|
158
|
+
type EmailLabel = 'work' | 'personal' | 'billing';
|
|
159
|
+
|
|
160
|
+
interface IAddress {
|
|
161
|
+
id: string;
|
|
162
|
+
countryIso: string;
|
|
163
|
+
subdivision1Iso: string | null;
|
|
164
|
+
subdivision2Iso: string | null;
|
|
165
|
+
zipPostalCode: string;
|
|
166
|
+
unit: string | null;
|
|
167
|
+
line1: string | null;
|
|
168
|
+
line2: string | null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
interface ICustomer {
|
|
172
|
+
id: string;
|
|
173
|
+
workspaceId: string;
|
|
174
|
+
type: CustomerType;
|
|
175
|
+
sex: CustomerSex;
|
|
176
|
+
firstName: string | null;
|
|
177
|
+
lastName: string | null;
|
|
178
|
+
companyName: string | null;
|
|
179
|
+
avatarUrl: string | null;
|
|
180
|
+
locale: string;
|
|
181
|
+
referenceCode: string | null;
|
|
182
|
+
isBlacklisted: boolean;
|
|
183
|
+
blacklistReason: string | null;
|
|
184
|
+
createdBy: string | null;
|
|
185
|
+
createdAt: string;
|
|
186
|
+
updatedAt: string;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
interface ICustomerAddress {
|
|
190
|
+
addrId: string;
|
|
191
|
+
customerId: string;
|
|
192
|
+
labelId: string;
|
|
193
|
+
label: string;
|
|
194
|
+
isPrimary: boolean;
|
|
195
|
+
address: IAddress;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
interface ICustomerDetail extends ICustomer {
|
|
199
|
+
emails: ICustomerEmail[];
|
|
200
|
+
phones: ICustomerPhone[];
|
|
201
|
+
addresses: ICustomerAddress[];
|
|
202
|
+
notes: ICustomerNote[];
|
|
203
|
+
relationships: ICustomerRelationshipExpanded[];
|
|
204
|
+
tags: string[];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
interface ICustomerEmail {
|
|
208
|
+
id: string;
|
|
209
|
+
customerId: string;
|
|
210
|
+
email: string;
|
|
211
|
+
labelId: string;
|
|
212
|
+
label: string;
|
|
213
|
+
isPrimary: boolean;
|
|
214
|
+
createdAt: string;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
interface ICustomerNote {
|
|
218
|
+
id: string;
|
|
219
|
+
customerId: string;
|
|
220
|
+
authorId: string | null;
|
|
221
|
+
body: string;
|
|
222
|
+
createdAt: string;
|
|
223
|
+
updatedAt: string;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
interface ICustomerPhone {
|
|
227
|
+
id: string;
|
|
228
|
+
customerId: string;
|
|
229
|
+
phone: string;
|
|
230
|
+
labelId: string;
|
|
231
|
+
label: string;
|
|
232
|
+
isPrimary: boolean;
|
|
233
|
+
createdAt: string;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
interface ICustomerTag {
|
|
237
|
+
customerId: string;
|
|
238
|
+
tag: string;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
type PhoneLabel = 'mobile' | 'office' | 'home' | 'fax';
|
|
242
|
+
|
|
243
|
+
namespace schemas — exports: addAddressSchema, addEmailSchema, addPhoneSchema, addRelationshipSchema, addTagSchema, blacklistSchema, createCustomerSchema, noteSchema, updateAddressSchema, updateCustomerSchema, updateEmailSchema, updatePhoneSchema
|
|
244
|
+
```
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
-- fonderie_customers
|
|
2
|
+
CREATE TABLE IF NOT EXISTS fonderie_customers (
|
|
3
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
4
|
+
workspace_id UUID NOT NULL,
|
|
5
|
+
type TEXT NOT NULL DEFAULT 'individual',
|
|
6
|
+
first_name TEXT,
|
|
7
|
+
last_name TEXT,
|
|
8
|
+
company_name TEXT,
|
|
9
|
+
job_title TEXT,
|
|
10
|
+
avatar_url TEXT,
|
|
11
|
+
locale TEXT NOT NULL DEFAULT 'en-US',
|
|
12
|
+
reference_code TEXT,
|
|
13
|
+
is_blacklisted BOOLEAN NOT NULL DEFAULT false,
|
|
14
|
+
created_by UUID,
|
|
15
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
16
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
CREATE INDEX IF NOT EXISTS idx_fc_workspace
|
|
20
|
+
ON fonderie_customers (workspace_id);
|
|
21
|
+
|
|
22
|
+
CREATE INDEX IF NOT EXISTS idx_fc_workspace_blacklisted
|
|
23
|
+
ON fonderie_customers (workspace_id, is_blacklisted);
|
|
24
|
+
|
|
25
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_fc_reference_code
|
|
26
|
+
ON fonderie_customers (workspace_id, reference_code)
|
|
27
|
+
WHERE reference_code IS NOT NULL;
|
|
28
|
+
|
|
29
|
+
-- fonderie_addresses (shared; created IF NOT EXISTS so another package can also define it)
|
|
30
|
+
CREATE TABLE IF NOT EXISTS fonderie_addresses (
|
|
31
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
32
|
+
country_iso TEXT NOT NULL,
|
|
33
|
+
subdivision1_iso TEXT,
|
|
34
|
+
subdivision2_iso TEXT,
|
|
35
|
+
zip_postal_code TEXT NOT NULL,
|
|
36
|
+
line1 TEXT,
|
|
37
|
+
line2 TEXT
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
-- fonderie_customer_emails
|
|
41
|
+
CREATE TABLE IF NOT EXISTS fonderie_customer_emails (
|
|
42
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
43
|
+
customer_id UUID NOT NULL REFERENCES fonderie_customers(id) ON DELETE CASCADE,
|
|
44
|
+
email TEXT NOT NULL,
|
|
45
|
+
label TEXT NOT NULL DEFAULT 'work',
|
|
46
|
+
is_primary BOOLEAN NOT NULL DEFAULT false,
|
|
47
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
CREATE INDEX IF NOT EXISTS idx_fce_customer
|
|
51
|
+
ON fonderie_customer_emails (customer_id);
|
|
52
|
+
|
|
53
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_fce_primary
|
|
54
|
+
ON fonderie_customer_emails (customer_id)
|
|
55
|
+
WHERE is_primary = true;
|
|
56
|
+
|
|
57
|
+
-- fonderie_customer_phones
|
|
58
|
+
CREATE TABLE IF NOT EXISTS fonderie_customer_phones (
|
|
59
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
60
|
+
customer_id UUID NOT NULL REFERENCES fonderie_customers(id) ON DELETE CASCADE,
|
|
61
|
+
phone TEXT NOT NULL,
|
|
62
|
+
label TEXT NOT NULL DEFAULT 'mobile',
|
|
63
|
+
is_primary BOOLEAN NOT NULL DEFAULT false,
|
|
64
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
CREATE INDEX IF NOT EXISTS idx_fcp_customer
|
|
68
|
+
ON fonderie_customer_phones (customer_id);
|
|
69
|
+
|
|
70
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_fcp_primary
|
|
71
|
+
ON fonderie_customer_phones (customer_id)
|
|
72
|
+
WHERE is_primary = true;
|
|
73
|
+
|
|
74
|
+
-- fonderie_customer_addresses (junction)
|
|
75
|
+
CREATE TABLE IF NOT EXISTS fonderie_customer_addresses (
|
|
76
|
+
addr_id UUID NOT NULL REFERENCES fonderie_addresses(id) ON DELETE CASCADE,
|
|
77
|
+
customer_id UUID NOT NULL REFERENCES fonderie_customers(id) ON DELETE CASCADE,
|
|
78
|
+
label TEXT NOT NULL DEFAULT 'service',
|
|
79
|
+
is_primary BOOLEAN NOT NULL DEFAULT false,
|
|
80
|
+
PRIMARY KEY (addr_id, customer_id)
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
CREATE INDEX IF NOT EXISTS idx_fca_customer
|
|
84
|
+
ON fonderie_customer_addresses (customer_id);
|
|
85
|
+
|
|
86
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_fca_primary
|
|
87
|
+
ON fonderie_customer_addresses (customer_id)
|
|
88
|
+
WHERE is_primary = true;
|
|
89
|
+
|
|
90
|
+
-- fonderie_customer_notes
|
|
91
|
+
CREATE TABLE IF NOT EXISTS fonderie_customer_notes (
|
|
92
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
93
|
+
customer_id UUID NOT NULL REFERENCES fonderie_customers(id) ON DELETE CASCADE,
|
|
94
|
+
author_id UUID,
|
|
95
|
+
body TEXT NOT NULL,
|
|
96
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
97
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
CREATE INDEX IF NOT EXISTS idx_fcn_customer
|
|
101
|
+
ON fonderie_customer_notes (customer_id);
|
|
102
|
+
|
|
103
|
+
-- fonderie_customer_tags
|
|
104
|
+
CREATE TABLE IF NOT EXISTS fonderie_customer_tags (
|
|
105
|
+
customer_id UUID NOT NULL REFERENCES fonderie_customers(id) ON DELETE CASCADE,
|
|
106
|
+
tag TEXT NOT NULL,
|
|
107
|
+
PRIMARY KEY (customer_id, tag)
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
CREATE INDEX IF NOT EXISTS idx_fct_tag
|
|
111
|
+
ON fonderie_customer_tags (tag);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
-- No-op: sequence table stays (workspace_id, prefix) — per-prefix counters are intentional.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ALTER TABLE fonderie_customers DROP COLUMN IF EXISTS job_title;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
-- Remove duplicate emails per customer, keeping the primary or the oldest row.
|
|
2
|
+
DELETE FROM fonderie_customer_emails
|
|
3
|
+
WHERE id NOT IN (
|
|
4
|
+
SELECT DISTINCT ON (customer_id, email)
|
|
5
|
+
id
|
|
6
|
+
FROM fonderie_customer_emails
|
|
7
|
+
ORDER BY customer_id, email, is_primary DESC, created_at ASC
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_fce_unique_email
|
|
11
|
+
ON fonderie_customer_emails (customer_id, email);
|
|
12
|
+
|
|
13
|
+
-- Remove duplicate phones per customer, keeping the primary or the oldest row.
|
|
14
|
+
DELETE FROM fonderie_customer_phones
|
|
15
|
+
WHERE id NOT IN (
|
|
16
|
+
SELECT DISTINCT ON (customer_id, phone)
|
|
17
|
+
id
|
|
18
|
+
FROM fonderie_customer_phones
|
|
19
|
+
ORDER BY customer_id, phone, is_primary DESC, created_at ASC
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_fcp_unique_phone
|
|
23
|
+
ON fonderie_customer_phones (customer_id, phone);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
DO $$
|
|
2
|
+
BEGIN
|
|
3
|
+
IF EXISTS (
|
|
4
|
+
SELECT 1 FROM information_schema.columns
|
|
5
|
+
WHERE table_name = 'fonderie_customers' AND column_name = 'is_archived'
|
|
6
|
+
) THEN
|
|
7
|
+
ALTER TABLE fonderie_customers RENAME COLUMN is_archived TO is_blacklisted;
|
|
8
|
+
END IF;
|
|
9
|
+
END $$;
|
|
10
|
+
|
|
11
|
+
DROP INDEX IF EXISTS idx_fc_workspace_archived;
|
|
12
|
+
CREATE INDEX IF NOT EXISTS idx_fc_workspace_blacklisted
|
|
13
|
+
ON fonderie_customers (workspace_id, is_blacklisted);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
-- Unit number on shared addresses table
|
|
2
|
+
ALTER TABLE fonderie_addresses
|
|
3
|
+
ADD COLUMN IF NOT EXISTS unit TEXT;
|
|
4
|
+
|
|
5
|
+
-- Links two customers with a typed, directional relationship
|
|
6
|
+
CREATE TABLE IF NOT EXISTS fonderie_customer_relationships (
|
|
7
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
8
|
+
workspace_id UUID NOT NULL,
|
|
9
|
+
customer_id UUID NOT NULL REFERENCES fonderie_customers(id) ON DELETE CASCADE,
|
|
10
|
+
related_id UUID NOT NULL REFERENCES fonderie_customers(id) ON DELETE CASCADE,
|
|
11
|
+
relationship TEXT NOT NULL,
|
|
12
|
+
is_primary BOOLEAN NOT NULL DEFAULT false,
|
|
13
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
14
|
+
UNIQUE (customer_id, related_id)
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
CREATE INDEX IF NOT EXISTS idx_fcrel_customer
|
|
18
|
+
ON fonderie_customer_relationships (customer_id);
|
|
19
|
+
|
|
20
|
+
CREATE INDEX IF NOT EXISTS idx_fcrel_related
|
|
21
|
+
ON fonderie_customer_relationships (related_id);
|
|
22
|
+
|
|
23
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_fcrel_primary
|
|
24
|
+
ON fonderie_customer_relationships (customer_id)
|
|
25
|
+
WHERE is_primary = true;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
-- fonderie_customer_label_type enum
|
|
2
|
+
CREATE TYPE fonderie_customer_label_type AS ENUM ('email', 'phone', 'address');
|
|
3
|
+
|
|
4
|
+
-- shared label catalog
|
|
5
|
+
CREATE TABLE IF NOT EXISTS fonderie_customer_labels (
|
|
6
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
7
|
+
type fonderie_customer_label_type NOT NULL,
|
|
8
|
+
value TEXT NOT NULL,
|
|
9
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
10
|
+
CONSTRAINT uq_fcl_type_value UNIQUE (type, value)
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
-- seed defaults
|
|
14
|
+
INSERT INTO fonderie_customer_labels (type, value) VALUES
|
|
15
|
+
('email', 'work'),
|
|
16
|
+
('email', 'personal'),
|
|
17
|
+
('email', 'billing'),
|
|
18
|
+
('phone', 'mobile'),
|
|
19
|
+
('phone', 'office'),
|
|
20
|
+
('phone', 'home'),
|
|
21
|
+
('phone', 'fax'),
|
|
22
|
+
('address', 'service'),
|
|
23
|
+
('address', 'billing'),
|
|
24
|
+
('address', 'other')
|
|
25
|
+
ON CONFLICT (type, value) DO NOTHING;
|
|
26
|
+
|
|
27
|
+
-- promote any non-standard values already stored in existing rows
|
|
28
|
+
INSERT INTO fonderie_customer_labels (type, value)
|
|
29
|
+
SELECT DISTINCT 'email'::fonderie_customer_label_type, label
|
|
30
|
+
FROM fonderie_customer_emails
|
|
31
|
+
WHERE label IS NOT NULL
|
|
32
|
+
ON CONFLICT (type, value) DO NOTHING;
|
|
33
|
+
|
|
34
|
+
INSERT INTO fonderie_customer_labels (type, value)
|
|
35
|
+
SELECT DISTINCT 'phone'::fonderie_customer_label_type, label
|
|
36
|
+
FROM fonderie_customer_phones
|
|
37
|
+
WHERE label IS NOT NULL
|
|
38
|
+
ON CONFLICT (type, value) DO NOTHING;
|
|
39
|
+
|
|
40
|
+
INSERT INTO fonderie_customer_labels (type, value)
|
|
41
|
+
SELECT DISTINCT 'address'::fonderie_customer_label_type, label
|
|
42
|
+
FROM fonderie_customer_addresses
|
|
43
|
+
WHERE label IS NOT NULL
|
|
44
|
+
ON CONFLICT (type, value) DO NOTHING;
|
|
45
|
+
|
|
46
|
+
-- add label_id FK columns
|
|
47
|
+
ALTER TABLE fonderie_customer_emails
|
|
48
|
+
ADD COLUMN IF NOT EXISTS label_id UUID REFERENCES fonderie_customer_labels(id);
|
|
49
|
+
|
|
50
|
+
ALTER TABLE fonderie_customer_phones
|
|
51
|
+
ADD COLUMN IF NOT EXISTS label_id UUID REFERENCES fonderie_customer_labels(id);
|
|
52
|
+
|
|
53
|
+
ALTER TABLE fonderie_customer_addresses
|
|
54
|
+
ADD COLUMN IF NOT EXISTS label_id UUID REFERENCES fonderie_customer_labels(id);
|
|
55
|
+
|
|
56
|
+
-- backfill label_id from existing label text
|
|
57
|
+
UPDATE fonderie_customer_emails e
|
|
58
|
+
SET label_id = l.id
|
|
59
|
+
FROM fonderie_customer_labels l
|
|
60
|
+
WHERE l.type = 'email' AND l.value = e.label;
|
|
61
|
+
|
|
62
|
+
UPDATE fonderie_customer_phones p
|
|
63
|
+
SET label_id = l.id
|
|
64
|
+
FROM fonderie_customer_labels l
|
|
65
|
+
WHERE l.type = 'phone' AND l.value = p.label;
|
|
66
|
+
|
|
67
|
+
UPDATE fonderie_customer_addresses a
|
|
68
|
+
SET label_id = l.id
|
|
69
|
+
FROM fonderie_customer_labels l
|
|
70
|
+
WHERE l.type = 'address' AND l.value = a.label;
|
|
71
|
+
|
|
72
|
+
-- mark old text columns as deprecated
|
|
73
|
+
COMMENT ON COLUMN fonderie_customer_emails.label IS 'deprecated: use label_id → fonderie_customer_labels';
|
|
74
|
+
COMMENT ON COLUMN fonderie_customer_phones.label IS 'deprecated: use label_id → fonderie_customer_labels';
|
|
75
|
+
COMMENT ON COLUMN fonderie_customer_addresses.label IS 'deprecated: use label_id → fonderie_customer_labels';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
-- Allow label catalog entries to be deleted even when in use.
|
|
2
|
+
-- Affected phone/email/address rows keep their label text column; label_id becomes NULL.
|
|
3
|
+
|
|
4
|
+
ALTER TABLE fonderie_customer_phones
|
|
5
|
+
DROP CONSTRAINT IF EXISTS fonderie_customer_phones_label_id_fkey,
|
|
6
|
+
ADD CONSTRAINT fonderie_customer_phones_label_id_fkey
|
|
7
|
+
FOREIGN KEY (label_id) REFERENCES fonderie_customer_labels(id) ON DELETE SET NULL;
|
|
8
|
+
|
|
9
|
+
ALTER TABLE fonderie_customer_emails
|
|
10
|
+
DROP CONSTRAINT IF EXISTS fonderie_customer_emails_label_id_fkey,
|
|
11
|
+
ADD CONSTRAINT fonderie_customer_emails_label_id_fkey
|
|
12
|
+
FOREIGN KEY (label_id) REFERENCES fonderie_customer_labels(id) ON DELETE SET NULL;
|
|
13
|
+
|
|
14
|
+
ALTER TABLE fonderie_customer_addresses
|
|
15
|
+
DROP CONSTRAINT IF EXISTS fonderie_customer_addresses_label_id_fkey,
|
|
16
|
+
ADD CONSTRAINT fonderie_customer_addresses_label_id_fkey
|
|
17
|
+
FOREIGN KEY (label_id) REFERENCES fonderie_customer_labels(id) ON DELETE SET NULL;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fonderie/customers",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "Workspace-scoped customer records — individuals and businesses with multiple emails, phones, addresses, notes, and tags.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fonderie-js",
|
|
@@ -70,17 +70,18 @@
|
|
|
70
70
|
},
|
|
71
71
|
"files": [
|
|
72
72
|
"dist",
|
|
73
|
+
"brain",
|
|
73
74
|
"LICENSE",
|
|
74
75
|
"README.md"
|
|
75
76
|
],
|
|
76
77
|
"repository": {
|
|
77
78
|
"type": "git",
|
|
78
|
-
"url": "git+https://github.com/
|
|
79
|
+
"url": "git+https://github.com/fonderiejs/sdk.git",
|
|
79
80
|
"directory": "packages/customers"
|
|
80
81
|
},
|
|
81
|
-
"homepage": "https://github.com/
|
|
82
|
+
"homepage": "https://github.com/fonderiejs/sdk/tree/main/packages/customers#readme",
|
|
82
83
|
"bugs": {
|
|
83
|
-
"url": "https://github.com/
|
|
84
|
+
"url": "https://github.com/fonderiejs/sdk/issues"
|
|
84
85
|
},
|
|
85
86
|
"dependencies": {
|
|
86
87
|
"zod": "^4.4.3"
|