@mohasinac/contracts 0.1.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/dist/index.cjs +109 -0
- package/dist/index.d.cts +832 -0
- package/dist/index.d.ts +832 -0
- package/dist/index.js +79 -0
- package/dist/index.mjs +21 -0
- package/dist/types/auth.d.ts +53 -0
- package/dist/types/auth.d.ts.map +1 -0
- package/dist/types/config.d.ts +54 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/email.d.ts +31 -0
- package/dist/types/email.d.ts.map +1 -0
- package/dist/types/feature.d.ts +54 -0
- package/dist/types/feature.d.ts.map +1 -0
- package/dist/types/index.d.ts +14 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/infra.d.ts +44 -0
- package/dist/types/infra.d.ts.map +1 -0
- package/dist/types/payment.d.ts +40 -0
- package/dist/types/payment.d.ts.map +1 -0
- package/dist/types/registry.d.ts +51 -0
- package/dist/types/registry.d.ts.map +1 -0
- package/dist/types/repository.d.ts +60 -0
- package/dist/types/repository.d.ts.map +1 -0
- package/dist/types/search.d.ts +46 -0
- package/dist/types/search.d.ts.map +1 -0
- package/dist/types/shipping.d.ts +65 -0
- package/dist/types/shipping.d.ts.map +1 -0
- package/dist/types/storage.d.ts +28 -0
- package/dist/types/storage.d.ts.map +1 -0
- package/dist/types/style.d.ts +24 -0
- package/dist/types/style.d.ts.map +1 -0
- package/package.json +31 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,832 @@
|
|
|
1
|
+
/** Field comparison operators supported by Sieve-style query strings. */
|
|
2
|
+
type WhereOp = "==" | "!=" | "<" | "<=" | ">" | ">=" | "array-contains" | "in" | "not-in" | "array-contains-any";
|
|
3
|
+
/**
|
|
4
|
+
* Provider-agnostic query descriptor.
|
|
5
|
+
* `filters` uses Sieve syntax: "field==value,field2>value2"
|
|
6
|
+
* DB adapters translate this into native query predicates.
|
|
7
|
+
*/
|
|
8
|
+
interface SieveQuery {
|
|
9
|
+
/** Sieve filter string, e.g. "status==published,price>100" */
|
|
10
|
+
filters?: string;
|
|
11
|
+
/** Field name to sort by */
|
|
12
|
+
sort?: string;
|
|
13
|
+
/** Sort direction (default: "asc") */
|
|
14
|
+
order?: "asc" | "desc";
|
|
15
|
+
/** 1-based page number (default: 1) */
|
|
16
|
+
page?: number;
|
|
17
|
+
/** Items per page (default: 20) */
|
|
18
|
+
perPage?: number;
|
|
19
|
+
}
|
|
20
|
+
/** Paginated result envelope returned by IReadRepository.findAll() */
|
|
21
|
+
interface PagedResult<T> {
|
|
22
|
+
data: T[];
|
|
23
|
+
total: number;
|
|
24
|
+
page: number;
|
|
25
|
+
perPage: number;
|
|
26
|
+
totalPages: number;
|
|
27
|
+
}
|
|
28
|
+
interface IReadRepository<T> {
|
|
29
|
+
findById(id: string): Promise<T | null>;
|
|
30
|
+
findAll(query?: SieveQuery): Promise<PagedResult<T>>;
|
|
31
|
+
findWhere(field: keyof T, op: WhereOp, value: unknown): Promise<T[]>;
|
|
32
|
+
}
|
|
33
|
+
interface IWriteRepository<T> {
|
|
34
|
+
create(data: Omit<T, "id" | "createdAt" | "updatedAt">): Promise<T>;
|
|
35
|
+
update(id: string, data: Partial<T>): Promise<T>;
|
|
36
|
+
delete(id: string): Promise<void>;
|
|
37
|
+
batchCreate(items: Array<Omit<T, "id" | "createdAt" | "updatedAt">>): Promise<T[]>;
|
|
38
|
+
batchDelete(ids: string[]): Promise<void>;
|
|
39
|
+
}
|
|
40
|
+
/** Full CRUD repository — combines read + write. */
|
|
41
|
+
interface IRepository<T> extends IReadRepository<T>, IWriteRepository<T> {
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Repository with real-time subscription support (e.g. Firestore RTDB).
|
|
45
|
+
* Returns an unsubscribe function from both subscribe methods.
|
|
46
|
+
*/
|
|
47
|
+
interface IRealtimeRepository<T> extends IRepository<T> {
|
|
48
|
+
subscribe(id: string, cb: (data: T | null) => void): () => void;
|
|
49
|
+
subscribeWhere(field: keyof T, value: unknown, cb: (items: T[]) => void): () => void;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Database provider — creates IRepository<T> instances on demand.
|
|
53
|
+
* Registered once in providers.config.ts; feature packages call
|
|
54
|
+
* `getProviders().db.getRepository<T>(collection)` so they never
|
|
55
|
+
* import a concrete adapter (FirebaseRepository, PrismaRepository, …).
|
|
56
|
+
*/
|
|
57
|
+
interface IDbProvider {
|
|
58
|
+
getRepository<T>(collection: string): IRepository<T>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface AuthPayload {
|
|
62
|
+
uid: string;
|
|
63
|
+
email: string | null;
|
|
64
|
+
/** Application-level role string, e.g. "admin" | "seller" | "user" */
|
|
65
|
+
role: string;
|
|
66
|
+
emailVerified: boolean;
|
|
67
|
+
/** Any extra custom claims stored on the token */
|
|
68
|
+
claims?: Record<string, unknown>;
|
|
69
|
+
}
|
|
70
|
+
interface AuthUser {
|
|
71
|
+
uid: string;
|
|
72
|
+
email: string | null;
|
|
73
|
+
displayName: string | null;
|
|
74
|
+
photoURL: string | null;
|
|
75
|
+
role: string;
|
|
76
|
+
emailVerified: boolean;
|
|
77
|
+
disabled: boolean;
|
|
78
|
+
createdAt: string;
|
|
79
|
+
}
|
|
80
|
+
interface CreateUserInput {
|
|
81
|
+
email: string;
|
|
82
|
+
password?: string;
|
|
83
|
+
displayName?: string;
|
|
84
|
+
photoURL?: string;
|
|
85
|
+
role?: string;
|
|
86
|
+
emailVerified?: boolean;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Server-side authentication operations.
|
|
90
|
+
* Implemented by @mohasinac/auth-firebase, @mohasinac/auth-nextauth, @mohasinac/auth-clerk.
|
|
91
|
+
*/
|
|
92
|
+
interface IAuthProvider {
|
|
93
|
+
verifyToken(token: string): Promise<AuthPayload>;
|
|
94
|
+
createCustomToken(uid: string, claims?: Record<string, unknown>): Promise<string>;
|
|
95
|
+
getUser(uid: string): Promise<AuthUser | null>;
|
|
96
|
+
createUser(data: CreateUserInput): Promise<AuthUser>;
|
|
97
|
+
updateUser(uid: string, data: Partial<CreateUserInput>): Promise<AuthUser>;
|
|
98
|
+
deleteUser(uid: string): Promise<void>;
|
|
99
|
+
revokeTokens(uid: string): Promise<void>;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Session cookie management (HTTP-only, server-side).
|
|
103
|
+
* Implemented alongside IAuthProvider by the same auth package.
|
|
104
|
+
*/
|
|
105
|
+
interface ISessionProvider {
|
|
106
|
+
/** Creates a session cookie value from a verified auth payload. */
|
|
107
|
+
createSession(payload: AuthPayload): Promise<string>;
|
|
108
|
+
/** Verifies a session cookie and returns the auth payload. */
|
|
109
|
+
verifySession(cookie: string): Promise<AuthPayload>;
|
|
110
|
+
/** Invalidates a session cookie. */
|
|
111
|
+
destroySession(cookie: string): Promise<void>;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
interface EmailAttachment {
|
|
115
|
+
filename: string;
|
|
116
|
+
content: Buffer | string;
|
|
117
|
+
contentType?: string;
|
|
118
|
+
}
|
|
119
|
+
interface EmailOptions {
|
|
120
|
+
to: string | string[];
|
|
121
|
+
subject: string;
|
|
122
|
+
html: string;
|
|
123
|
+
text?: string;
|
|
124
|
+
/** Defaults to SiteConfig.email.fromAddress if omitted. */
|
|
125
|
+
from?: string;
|
|
126
|
+
replyTo?: string;
|
|
127
|
+
attachments?: EmailAttachment[];
|
|
128
|
+
headers?: Record<string, string>;
|
|
129
|
+
}
|
|
130
|
+
interface EmailResult {
|
|
131
|
+
id: string;
|
|
132
|
+
accepted: string[];
|
|
133
|
+
rejected: string[];
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Transactional email sending contract.
|
|
137
|
+
* Implemented by @mohasinac/email-resend, @mohasinac/email-nodemailer,
|
|
138
|
+
* @mohasinac/email-sendgrid, @mohasinac/email-postmark.
|
|
139
|
+
*/
|
|
140
|
+
interface IEmailProvider {
|
|
141
|
+
send(options: EmailOptions): Promise<EmailResult>;
|
|
142
|
+
sendBatch(options: EmailOptions[]): Promise<EmailResult[]>;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
interface UploadOptions {
|
|
146
|
+
contentType?: string;
|
|
147
|
+
cacheControl?: string;
|
|
148
|
+
/** Make the file publicly accessible (default: true) */
|
|
149
|
+
isPublic?: boolean;
|
|
150
|
+
metadata?: Record<string, string>;
|
|
151
|
+
}
|
|
152
|
+
interface StorageFile {
|
|
153
|
+
path: string;
|
|
154
|
+
url: string;
|
|
155
|
+
contentType: string;
|
|
156
|
+
size: number;
|
|
157
|
+
updatedAt: string;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* File storage adapter contract.
|
|
161
|
+
* Implemented by @mohasinac/storage-firebase, @mohasinac/storage-s3,
|
|
162
|
+
* @mohasinac/storage-cloudinary, @mohasinac/storage-uploadthing.
|
|
163
|
+
*/
|
|
164
|
+
interface IStorageProvider {
|
|
165
|
+
upload(file: Buffer, path: string, options?: UploadOptions): Promise<StorageFile>;
|
|
166
|
+
delete(path: string): Promise<void>;
|
|
167
|
+
getPublicUrl(path: string): string;
|
|
168
|
+
getSignedUrl(path: string, expiresInSeconds?: number): Promise<string>;
|
|
169
|
+
copy(from: string, to: string): Promise<StorageFile>;
|
|
170
|
+
list(prefix: string): Promise<StorageFile[]>;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
interface PaymentOrder {
|
|
174
|
+
id: string;
|
|
175
|
+
amount: number;
|
|
176
|
+
currency: string;
|
|
177
|
+
status: "created" | "attempted" | "paid" | "failed";
|
|
178
|
+
receipt?: string;
|
|
179
|
+
metadata?: Record<string, unknown>;
|
|
180
|
+
createdAt: string;
|
|
181
|
+
}
|
|
182
|
+
interface PaymentCapture {
|
|
183
|
+
id: string;
|
|
184
|
+
orderId: string;
|
|
185
|
+
amount: number;
|
|
186
|
+
currency: string;
|
|
187
|
+
status: "captured" | "failed";
|
|
188
|
+
capturedAt: string;
|
|
189
|
+
}
|
|
190
|
+
interface Refund {
|
|
191
|
+
id: string;
|
|
192
|
+
paymentId: string;
|
|
193
|
+
amount: number;
|
|
194
|
+
currency: string;
|
|
195
|
+
status: "pending" | "processed" | "failed";
|
|
196
|
+
reason?: string;
|
|
197
|
+
createdAt: string;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Payment gateway adapter contract.
|
|
201
|
+
* Implemented by @mohasinac/payment-razorpay, @mohasinac/payment-stripe,
|
|
202
|
+
* @mohasinac/payment-braintree.
|
|
203
|
+
*/
|
|
204
|
+
interface IPaymentProvider {
|
|
205
|
+
createOrder(amount: number, currency: string, metadata?: Record<string, unknown>): Promise<PaymentOrder>;
|
|
206
|
+
/** Returns true if the webhook signature is valid. */
|
|
207
|
+
verifyWebhook(payload: string, signature: string): boolean;
|
|
208
|
+
capturePayment(orderId: string): Promise<PaymentCapture>;
|
|
209
|
+
refund(paymentId: string, amount?: number): Promise<Refund>;
|
|
210
|
+
getOrder(orderId: string): Promise<PaymentOrder>;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
interface ShippingAddress {
|
|
214
|
+
name: string;
|
|
215
|
+
phone: string;
|
|
216
|
+
addressLine1: string;
|
|
217
|
+
addressLine2?: string;
|
|
218
|
+
city: string;
|
|
219
|
+
state: string;
|
|
220
|
+
pincode: string;
|
|
221
|
+
country: string;
|
|
222
|
+
}
|
|
223
|
+
interface CreateShipmentInput {
|
|
224
|
+
orderId: string;
|
|
225
|
+
dimensions: {
|
|
226
|
+
weight: number;
|
|
227
|
+
length: number;
|
|
228
|
+
width: number;
|
|
229
|
+
height: number;
|
|
230
|
+
};
|
|
231
|
+
pickup: ShippingAddress;
|
|
232
|
+
delivery: ShippingAddress;
|
|
233
|
+
codAmount?: number;
|
|
234
|
+
isCod?: boolean;
|
|
235
|
+
}
|
|
236
|
+
interface Shipment {
|
|
237
|
+
id: string;
|
|
238
|
+
trackingId: string;
|
|
239
|
+
orderId: string;
|
|
240
|
+
status: string;
|
|
241
|
+
courier?: string;
|
|
242
|
+
estimatedDelivery?: string;
|
|
243
|
+
createdAt: string;
|
|
244
|
+
}
|
|
245
|
+
interface TrackingEvent {
|
|
246
|
+
status: string;
|
|
247
|
+
location: string;
|
|
248
|
+
timestamp: string;
|
|
249
|
+
description?: string;
|
|
250
|
+
}
|
|
251
|
+
interface TrackingInfo {
|
|
252
|
+
trackingId: string;
|
|
253
|
+
currentStatus: string;
|
|
254
|
+
estimatedDelivery?: string;
|
|
255
|
+
events: TrackingEvent[];
|
|
256
|
+
}
|
|
257
|
+
interface ServiceabilityResult {
|
|
258
|
+
isServiceable: boolean;
|
|
259
|
+
couriers: Array<{
|
|
260
|
+
name: string;
|
|
261
|
+
estimatedDays: number;
|
|
262
|
+
rate: number;
|
|
263
|
+
}>;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Shipping carrier adapter contract.
|
|
267
|
+
* Implemented by @mohasinac/shipping-shiprocket, @mohasinac/shipping-shippo,
|
|
268
|
+
* @mohasinac/shipping-easypost.
|
|
269
|
+
*/
|
|
270
|
+
interface IShippingProvider {
|
|
271
|
+
createShipment(data: CreateShipmentInput): Promise<Shipment>;
|
|
272
|
+
trackShipment(trackingId: string): Promise<TrackingInfo>;
|
|
273
|
+
cancelShipment(shipmentId: string): Promise<void>;
|
|
274
|
+
checkServiceability(pincode: string, weight: number): Promise<ServiceabilityResult>;
|
|
275
|
+
generateLabel(shipmentId: string): Promise<ArrayBuffer>;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
interface SearchOptions {
|
|
279
|
+
page?: number;
|
|
280
|
+
perPage?: number;
|
|
281
|
+
filters?: string;
|
|
282
|
+
facets?: string[];
|
|
283
|
+
sortBy?: string;
|
|
284
|
+
/** Attributes to include in the result (default: all) */
|
|
285
|
+
attributesToRetrieve?: string[];
|
|
286
|
+
/** Attributes to highlight in results */
|
|
287
|
+
attributesToHighlight?: string[];
|
|
288
|
+
}
|
|
289
|
+
interface SearchHit<T> {
|
|
290
|
+
id: string;
|
|
291
|
+
data: T;
|
|
292
|
+
score?: number;
|
|
293
|
+
highlights?: Record<string, string>;
|
|
294
|
+
}
|
|
295
|
+
interface SearchResult<T> {
|
|
296
|
+
hits: SearchHit<T>[];
|
|
297
|
+
total: number;
|
|
298
|
+
page: number;
|
|
299
|
+
perPage: number;
|
|
300
|
+
totalPages: number;
|
|
301
|
+
processingTimeMs?: number;
|
|
302
|
+
facets?: Record<string, Record<string, number>>;
|
|
303
|
+
}
|
|
304
|
+
interface SuggestOptions {
|
|
305
|
+
limit?: number;
|
|
306
|
+
filters?: string;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Full-text search adapter contract.
|
|
310
|
+
* Implemented by @mohasinac/search-algolia, @mohasinac/search-typesense,
|
|
311
|
+
* @mohasinac/search-meilisearch.
|
|
312
|
+
*/
|
|
313
|
+
interface ISearchProvider<T = Record<string, unknown>> {
|
|
314
|
+
index(id: string, data: T): Promise<void>;
|
|
315
|
+
indexBatch(items: Array<{
|
|
316
|
+
id: string;
|
|
317
|
+
data: T;
|
|
318
|
+
}>): Promise<void>;
|
|
319
|
+
remove(id: string): Promise<void>;
|
|
320
|
+
search(query: string, options?: SearchOptions): Promise<SearchResult<T>>;
|
|
321
|
+
suggest(query: string, options?: SuggestOptions): Promise<string[]>;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Key-value cache adapter contract.
|
|
326
|
+
* Implemented by @mohasinac/core (in-memory), Redis, Upstash.
|
|
327
|
+
*/
|
|
328
|
+
interface ICacheProvider {
|
|
329
|
+
get<T>(key: string): Promise<T | null>;
|
|
330
|
+
set<T>(key: string, value: T, ttlSeconds?: number): Promise<void>;
|
|
331
|
+
delete(key: string): Promise<void>;
|
|
332
|
+
clear(prefix?: string): Promise<void>;
|
|
333
|
+
has(key: string): Promise<boolean>;
|
|
334
|
+
}
|
|
335
|
+
interface QueueJob<T = unknown> {
|
|
336
|
+
id: string;
|
|
337
|
+
type: string;
|
|
338
|
+
payload: T;
|
|
339
|
+
attempts: number;
|
|
340
|
+
maxAttempts: number;
|
|
341
|
+
scheduledAt: string;
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Background job queue adapter contract.
|
|
345
|
+
* Implemented by @mohasinac/core (in-memory), BullMQ, Upstash QStash.
|
|
346
|
+
*/
|
|
347
|
+
interface IQueueProvider {
|
|
348
|
+
enqueue<T>(type: string, payload: T, options?: {
|
|
349
|
+
delayMs?: number;
|
|
350
|
+
maxAttempts?: number;
|
|
351
|
+
}): Promise<QueueJob<T>>;
|
|
352
|
+
process<T>(type: string, handler: (job: QueueJob<T>) => Promise<void>): void;
|
|
353
|
+
cancel(jobId: string): Promise<void>;
|
|
354
|
+
getJob<T>(jobId: string): Promise<QueueJob<T> | null>;
|
|
355
|
+
}
|
|
356
|
+
type EventHandler<T = unknown> = (payload: T) => void | Promise<void>;
|
|
357
|
+
/**
|
|
358
|
+
* In-process event bus contract.
|
|
359
|
+
* Implemented by @mohasinac/core (EventEmitter-based).
|
|
360
|
+
*/
|
|
361
|
+
interface IEventBus {
|
|
362
|
+
on<T>(event: string, handler: EventHandler<T>): void;
|
|
363
|
+
off<T>(event: string, handler: EventHandler<T>): void;
|
|
364
|
+
emit<T>(event: string, payload: T): void;
|
|
365
|
+
once<T>(event: string, handler: EventHandler<T>): void;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* CSS framework adapter contract.
|
|
370
|
+
* UI components call cn()/token() through this adapter, making them
|
|
371
|
+
* CSS-framework-agnostic.
|
|
372
|
+
*
|
|
373
|
+
* Implemented by:
|
|
374
|
+
* @mohasinac/css-tailwind — twMerge + clsx
|
|
375
|
+
* @mohasinac/css-vanilla — plain string join
|
|
376
|
+
* @mohasinac/css-emotion — Emotion cx()
|
|
377
|
+
* @mohasinac/css-modules — CSS Modules
|
|
378
|
+
*/
|
|
379
|
+
interface IStyleAdapter {
|
|
380
|
+
/**
|
|
381
|
+
* Merges and deduplicates class names.
|
|
382
|
+
* Equivalent to twMerge(clsx(...)) in Tailwind projects.
|
|
383
|
+
*/
|
|
384
|
+
cn(...classes: Array<string | undefined | null | false>): string;
|
|
385
|
+
/**
|
|
386
|
+
* Resolves a design token name to a CSS custom property reference.
|
|
387
|
+
* e.g. token("color-primary") → "var(--lir-color-primary)"
|
|
388
|
+
*/
|
|
389
|
+
token(name: string): string;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* The single DI container. Populated once at app startup via registerProviders().
|
|
394
|
+
* Feature packages access concrete implementations via getProviders() — they
|
|
395
|
+
* never import a concrete provider directly (Dependency Inversion Principle).
|
|
396
|
+
*/
|
|
397
|
+
interface ProviderRegistry {
|
|
398
|
+
auth: IAuthProvider;
|
|
399
|
+
session: ISessionProvider;
|
|
400
|
+
email: IEmailProvider;
|
|
401
|
+
storage: IStorageProvider;
|
|
402
|
+
style: IStyleAdapter;
|
|
403
|
+
/** Optional — database provider; enables true 2-line API route stubs in feat-* packages */
|
|
404
|
+
db?: IDbProvider;
|
|
405
|
+
/** Optional — only required for projects with payment flows */
|
|
406
|
+
payment?: IPaymentProvider;
|
|
407
|
+
/** Optional — only required for projects with order shipping */
|
|
408
|
+
shipping?: IShippingProvider;
|
|
409
|
+
/** Optional — only required for projects with full-text search */
|
|
410
|
+
search?: ISearchProvider;
|
|
411
|
+
/** Optional — falls back to in-memory cache if not provided */
|
|
412
|
+
cache?: ICacheProvider;
|
|
413
|
+
/** Optional — falls back to in-memory queue if not provided */
|
|
414
|
+
queue?: IQueueProvider;
|
|
415
|
+
/** Optional — falls back to EventEmitter if not provided */
|
|
416
|
+
eventBus?: IEventBus;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Call once at app startup (e.g. in providers.config.ts).
|
|
420
|
+
* Subsequent calls replace the registry — useful in tests.
|
|
421
|
+
*/
|
|
422
|
+
declare function registerProviders(registry: ProviderRegistry): void;
|
|
423
|
+
/**
|
|
424
|
+
* Returns the registry. Throws if registerProviders() has not been called.
|
|
425
|
+
* Feature packages call this to resolve concrete implementations.
|
|
426
|
+
*/
|
|
427
|
+
declare function getProviders(): ProviderRegistry;
|
|
428
|
+
/**
|
|
429
|
+
* Resets the registry — only for use in test environments.
|
|
430
|
+
* @internal
|
|
431
|
+
*/
|
|
432
|
+
declare function _resetProviders(): void;
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Describes a page route stub that the CLI generates when `add <feature>` is run.
|
|
436
|
+
*/
|
|
437
|
+
interface RouteStub {
|
|
438
|
+
/** Next.js route segment, e.g. "[locale]/events" */
|
|
439
|
+
segment: string;
|
|
440
|
+
exports: {
|
|
441
|
+
/** Default export name from the feature package */
|
|
442
|
+
default: string;
|
|
443
|
+
/** generateMetadata export name, if applicable */
|
|
444
|
+
generateMetadata?: string;
|
|
445
|
+
};
|
|
446
|
+
/** If true, the CLI wraps the stub in admin-only protection comments */
|
|
447
|
+
adminOnly?: boolean;
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Describes an API route stub that the CLI generates.
|
|
451
|
+
*/
|
|
452
|
+
interface ApiRouteStub {
|
|
453
|
+
/** Next.js route segment, e.g. "api/events/[id]" */
|
|
454
|
+
segment: string;
|
|
455
|
+
methods: Array<"GET" | "POST" | "PATCH" | "PUT" | "DELETE">;
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* The contract between a feature package and @mohasinac/cli.
|
|
459
|
+
* Every feat-* package exports a `manifest` object satisfying this interface.
|
|
460
|
+
*/
|
|
461
|
+
interface FeatureManifest {
|
|
462
|
+
/** Unique feature identifier, matches FeaturesConfig key */
|
|
463
|
+
name: string;
|
|
464
|
+
/** next-intl namespace key for this feature's messages */
|
|
465
|
+
i18nNamespace: string;
|
|
466
|
+
/** Environment variable keys required by this feature (documentation only) */
|
|
467
|
+
envKeys: string[];
|
|
468
|
+
/** Page route stubs generated by CLI on install */
|
|
469
|
+
routes: RouteStub[];
|
|
470
|
+
/** API route stubs generated by CLI on install */
|
|
471
|
+
apiRoutes: ApiRouteStub[];
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Shape of features.config.ts in consumer projects.
|
|
475
|
+
* Keys are feature names; values are booleans (enabled/disabled).
|
|
476
|
+
*
|
|
477
|
+
* @example
|
|
478
|
+
* ```ts
|
|
479
|
+
* export default {
|
|
480
|
+
* auth: true,
|
|
481
|
+
* events: true,
|
|
482
|
+
* auctions: false,
|
|
483
|
+
* } satisfies FeaturesConfig;
|
|
484
|
+
* ```
|
|
485
|
+
*/
|
|
486
|
+
type FeaturesConfig = Record<string, boolean>;
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Brand configuration injected into layout/nav shell components.
|
|
490
|
+
* Passed as the `config` prop to NavbarLayout, FooterLayout, etc.
|
|
491
|
+
*/
|
|
492
|
+
interface SiteConfig {
|
|
493
|
+
name: string;
|
|
494
|
+
tagline?: string;
|
|
495
|
+
description: string;
|
|
496
|
+
/** Canonical base URL, e.g. "https://letitrip.in" */
|
|
497
|
+
url: string;
|
|
498
|
+
logoUrl: string;
|
|
499
|
+
logoAlt?: string;
|
|
500
|
+
email: {
|
|
501
|
+
/** Address used as the "from" field in transactional emails */
|
|
502
|
+
fromAddress: string;
|
|
503
|
+
supportAddress: string;
|
|
504
|
+
};
|
|
505
|
+
phone?: string;
|
|
506
|
+
address?: string;
|
|
507
|
+
social?: {
|
|
508
|
+
twitter?: string;
|
|
509
|
+
instagram?: string;
|
|
510
|
+
facebook?: string;
|
|
511
|
+
youtube?: string;
|
|
512
|
+
linkedin?: string;
|
|
513
|
+
whatsapp?: string;
|
|
514
|
+
};
|
|
515
|
+
/** ISO 4217 currency code used site-wide, e.g. "INR" */
|
|
516
|
+
currency?: string;
|
|
517
|
+
/** BCP 47 locale tag for default locale, e.g. "en" */
|
|
518
|
+
defaultLocale?: string;
|
|
519
|
+
/** Supported locales */
|
|
520
|
+
locales?: string[];
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* A single nav item passed to NavbarLayout / Sidebar / BottomNavbar.
|
|
524
|
+
*/
|
|
525
|
+
interface NavItem {
|
|
526
|
+
/** Display label (translation key or literal string) */
|
|
527
|
+
label: string;
|
|
528
|
+
/** Route href */
|
|
529
|
+
href: string;
|
|
530
|
+
/** Icon name / component key */
|
|
531
|
+
icon?: string;
|
|
532
|
+
/** Nested items for dropdown / accordion menus */
|
|
533
|
+
children?: NavItem[];
|
|
534
|
+
/** If true, item is only shown to admin users */
|
|
535
|
+
adminOnly?: boolean;
|
|
536
|
+
/** If true, item is only shown when the user is authenticated */
|
|
537
|
+
authOnly?: boolean;
|
|
538
|
+
/** Open link in a new tab */
|
|
539
|
+
external?: boolean;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Utility types for schema/type extensibility across feat-* packages.
|
|
544
|
+
*
|
|
545
|
+
* ## Three-layer extensibility model
|
|
546
|
+
*
|
|
547
|
+
* Every feat-* package exports:
|
|
548
|
+
* 1. **Schema** — a base Zod object (`xxxSchema`). Extend with `.extend({...})`.
|
|
549
|
+
* 2. **Columns** — a `xxxAdminColumns` array + `buildXxxColumns()` factory.
|
|
550
|
+
* 3. **Slots** — render-prop overrides accepted by all list/detail views.
|
|
551
|
+
*
|
|
552
|
+
* Assemble all three into a single `FeatureExtension` descriptor and pass it
|
|
553
|
+
* to the hook AND the view component:
|
|
554
|
+
*
|
|
555
|
+
* @example
|
|
556
|
+
* ```ts
|
|
557
|
+
* // 1. Declare the extension (one file per feature in your app)
|
|
558
|
+
* import { productItemSchema, buildProductColumns } from "@mohasinac/feat-products";
|
|
559
|
+
* import type { FeatureExtension } from "@mohasinac/contracts";
|
|
560
|
+
* import type { ProductItem } from "@mohasinac/feat-products";
|
|
561
|
+
* import { z } from "zod";
|
|
562
|
+
*
|
|
563
|
+
* interface ProductDocument extends ProductItem { brand: string }
|
|
564
|
+
*
|
|
565
|
+
* export const productExt: FeatureExtension<ProductItem, ProductDocument> = {
|
|
566
|
+
* schema: productItemSchema.extend({ brand: z.string() }),
|
|
567
|
+
* columns: buildProductColumns<ProductDocument>({
|
|
568
|
+
* extras: [{ key: "brand", header: "Brand", render: (p) => p.brand }],
|
|
569
|
+
* }),
|
|
570
|
+
* slots: { renderCard: (p) => <MyBrandCard product={p} /> },
|
|
571
|
+
* transform: (raw) => ({ ...raw, brand: raw.attributes?.brand ?? "" }),
|
|
572
|
+
* };
|
|
573
|
+
*
|
|
574
|
+
* // 2. Hook
|
|
575
|
+
* const { products } = useProducts<ProductDocument>(params, productExt);
|
|
576
|
+
*
|
|
577
|
+
* // 3. View
|
|
578
|
+
* <ProductGrid products={products} {...productExt.slots} />
|
|
579
|
+
* ```
|
|
580
|
+
*/
|
|
581
|
+
/**
|
|
582
|
+
* Options mixin for hooks that support an optional per-item transform.
|
|
583
|
+
* The transform maps the API's base type to a richer app-level type.
|
|
584
|
+
*/
|
|
585
|
+
interface WithTransformOpts<Base, Target extends Base = Base> {
|
|
586
|
+
/**
|
|
587
|
+
* Map each API item (BaseType) to a richer app-level type (Target).
|
|
588
|
+
* Called once per item after the query resolves.
|
|
589
|
+
*/
|
|
590
|
+
transform?: (item: Base) => Target;
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Convenience helper: build a response type for list endpoints that mirrors
|
|
594
|
+
* the package's standard list-response shape but with a generic item type.
|
|
595
|
+
*/
|
|
596
|
+
interface GenericListResponse<T> {
|
|
597
|
+
items: T[];
|
|
598
|
+
total: number;
|
|
599
|
+
page: number;
|
|
600
|
+
pageSize?: number;
|
|
601
|
+
perPage?: number;
|
|
602
|
+
totalPages: number;
|
|
603
|
+
hasMore?: boolean;
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* A single column definition for data tables.
|
|
607
|
+
* Defined here in contracts so feat-* packages can export default column sets
|
|
608
|
+
* without cross-importing each other.
|
|
609
|
+
*
|
|
610
|
+
* @example
|
|
611
|
+
* // feat-products can export default product columns
|
|
612
|
+
* export const productAdminColumns: TableColumn<ProductItem>[] = [...];
|
|
613
|
+
*
|
|
614
|
+
* // Consumer app extends with their richer type
|
|
615
|
+
* const columns: TableColumn<ProductDocument>[] = [
|
|
616
|
+
* ...productAdminColumns as TableColumn<ProductDocument>[],
|
|
617
|
+
* { key: "brand", header: "Brand", render: (p) => p.brand },
|
|
618
|
+
* ];
|
|
619
|
+
*/
|
|
620
|
+
interface TableColumn<T = Record<string, unknown>> {
|
|
621
|
+
/** Field key (used for sorting, cell lookup) */
|
|
622
|
+
key: string;
|
|
623
|
+
/** Column header label */
|
|
624
|
+
header: string;
|
|
625
|
+
/** Whether clicking the header fires an onSort callback */
|
|
626
|
+
sortable?: boolean;
|
|
627
|
+
/** Custom cell renderer. Return value is rendered as-is (React.ReactNode in JSX context). */
|
|
628
|
+
render?: (row: T) => unknown;
|
|
629
|
+
/** Additional className for the `<th>` and `<td>` */
|
|
630
|
+
className?: string;
|
|
631
|
+
/** CSS width / Tailwind width class applied to the column. */
|
|
632
|
+
width?: string;
|
|
633
|
+
/** Hide this column from the rendered table (keeps it in the array for export) */
|
|
634
|
+
hidden?: boolean;
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Options accepted by every feature's `buildXxxColumns<T>()` factory.
|
|
638
|
+
* Allows apps to override individual column renderers and/or append extras.
|
|
639
|
+
*/
|
|
640
|
+
interface ColumnExtensionOpts<T> {
|
|
641
|
+
/**
|
|
642
|
+
* Keyed partial overrides merged into the matching base column.
|
|
643
|
+
* Only the provided fields are replaced; the rest of the base column is kept.
|
|
644
|
+
*/
|
|
645
|
+
overrides?: Partial<Record<string, Partial<TableColumn<T>>>>;
|
|
646
|
+
/** Additional columns appended after the base set. */
|
|
647
|
+
extras?: TableColumn<T>[];
|
|
648
|
+
/** Omit columns by key from the final list. */
|
|
649
|
+
omit?: string[];
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Render-prop slots accepted by every feature's list/detail view component.
|
|
653
|
+
* Only the slots you provide are used; the rest fall back to the package default.
|
|
654
|
+
*
|
|
655
|
+
* All return types are `unknown` here (no React dep in contracts).
|
|
656
|
+
* In practice each package narrows them to `React.ReactNode`.
|
|
657
|
+
*
|
|
658
|
+
* @example
|
|
659
|
+
* // Override only the card renderer
|
|
660
|
+
* <BlogListView
|
|
661
|
+
* posts={posts}
|
|
662
|
+
* slots={{ renderCard: (post) => <FeaturedBlogCard post={post} /> }}
|
|
663
|
+
* />
|
|
664
|
+
*/
|
|
665
|
+
interface LayoutSlots<T> {
|
|
666
|
+
/** Replace the default card used inside grid/card-list views. */
|
|
667
|
+
renderCard?: (item: T, index: number) => unknown;
|
|
668
|
+
/** Replace the default table row (used when the view renders a table). */
|
|
669
|
+
renderRow?: (item: T, index: number) => unknown;
|
|
670
|
+
/** Replace the empty-state UI shown when there are no items. */
|
|
671
|
+
renderEmptyState?: () => unknown;
|
|
672
|
+
/** Replace the header area (title bar, action buttons, filter summary). */
|
|
673
|
+
renderHeader?: (meta: {
|
|
674
|
+
total: number;
|
|
675
|
+
}) => unknown;
|
|
676
|
+
/** Replace the footer area (pagination, load-more button). */
|
|
677
|
+
renderFooter?: (meta: {
|
|
678
|
+
page: number;
|
|
679
|
+
totalPages: number;
|
|
680
|
+
}) => unknown;
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* The single object an app passes to customise one feature.
|
|
684
|
+
* Contains schema, columns, layout slots, and item transform — all optional.
|
|
685
|
+
*
|
|
686
|
+
* Every feat-* hook accepts `FeatureExtension` as its second argument (the
|
|
687
|
+
* `opts` parameter). Every feat-* list view accepts `slots` prop destructured
|
|
688
|
+
* from this object.
|
|
689
|
+
*
|
|
690
|
+
* @typeParam TBase — The base interface exported by the feat-* package
|
|
691
|
+
* (e.g. `ProductItem`, `BlogPost`).
|
|
692
|
+
* @typeParam TExtended — Your app-level interface that extends TBase
|
|
693
|
+
* (e.g. `ProductDocument`). Defaults to TBase.
|
|
694
|
+
*
|
|
695
|
+
* @example
|
|
696
|
+
* ```ts
|
|
697
|
+
* import { productItemSchema, buildProductColumns } from "@mohasinac/feat-products";
|
|
698
|
+
* import type { FeatureExtension } from "@mohasinac/contracts";
|
|
699
|
+
* import type { ProductItem } from "@mohasinac/feat-products";
|
|
700
|
+
* import { z } from "zod";
|
|
701
|
+
*
|
|
702
|
+
* interface ProductDocument extends ProductItem { brand: string }
|
|
703
|
+
*
|
|
704
|
+
* export const productExt: FeatureExtension<ProductItem, ProductDocument> = {
|
|
705
|
+
* schema: productItemSchema.extend({ brand: z.string() }),
|
|
706
|
+
* columns: buildProductColumns<ProductDocument>({
|
|
707
|
+
* extras: [{ key: "brand", header: "Brand", render: (p) => p.brand }],
|
|
708
|
+
* }),
|
|
709
|
+
* slots: { renderCard: (p) => <MyBrandCard product={p} /> },
|
|
710
|
+
* transform: (raw) => ({ ...raw, brand: raw.attributes?.brand ?? "" }),
|
|
711
|
+
* };
|
|
712
|
+
* ```
|
|
713
|
+
*/
|
|
714
|
+
interface FeatureExtension<TBase, TExtended extends TBase = TBase> extends WithTransformOpts<TBase, TExtended> {
|
|
715
|
+
/**
|
|
716
|
+
* Extended Zod schema. Use the package's base schema with `.extend({...})`.
|
|
717
|
+
* Typed as `unknown` here so contracts stays Zod-free; at runtime it is a
|
|
718
|
+
* `ZodObject` or `ZodEffects`.
|
|
719
|
+
*/
|
|
720
|
+
schema?: unknown;
|
|
721
|
+
/** Merged admin table columns. Use the package's `buildXxxColumns()` helper. */
|
|
722
|
+
columns?: TableColumn<TExtended>[];
|
|
723
|
+
/** Render-prop slot overrides for list/detail view components. */
|
|
724
|
+
slots?: LayoutSlots<TExtended>;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* Table, pagination and sticky-header configuration types.
|
|
729
|
+
*
|
|
730
|
+
* Every table/list view in every feat-* package accepts these config objects.
|
|
731
|
+
* Apps can spread-merge over the exported defaults to override only what they need:
|
|
732
|
+
*
|
|
733
|
+
* @example
|
|
734
|
+
* // App-level base config (one file, reused everywhere)
|
|
735
|
+
* import { DEFAULT_TABLE_CONFIG, DEFAULT_PAGINATION_CONFIG } from "@mohasinac/contracts";
|
|
736
|
+
*
|
|
737
|
+
* export const myTableConfig: TableConfig = {
|
|
738
|
+
* ...DEFAULT_TABLE_CONFIG,
|
|
739
|
+
* pageSize: 10,
|
|
740
|
+
* showViewToggle: true,
|
|
741
|
+
* };
|
|
742
|
+
*
|
|
743
|
+
* export const myPaginationConfig: PaginationConfig = {
|
|
744
|
+
* ...DEFAULT_PAGINATION_CONFIG,
|
|
745
|
+
* perPage: 10,
|
|
746
|
+
* showPageSizeSelector: true,
|
|
747
|
+
* pageSizeOptions: [10, 25, 50],
|
|
748
|
+
* };
|
|
749
|
+
*
|
|
750
|
+
* // In a component
|
|
751
|
+
* <DataTable tableConfig={myTableConfig} paginationConfig={myPaginationConfig} ... />
|
|
752
|
+
*
|
|
753
|
+
* // In a hook
|
|
754
|
+
* const { products } = useProducts(params, { paginationConfig: myPaginationConfig });
|
|
755
|
+
*/
|
|
756
|
+
/**
|
|
757
|
+
* Composable pagination configuration.
|
|
758
|
+
* All fields are optional so apps can spread-merge over the defaults.
|
|
759
|
+
*/
|
|
760
|
+
interface PaginationConfig {
|
|
761
|
+
/** Items per page sent to the API. Default: 20 */
|
|
762
|
+
perPage?: number;
|
|
763
|
+
/** Hard cap on perPage (for page-size selector). Default: 100 */
|
|
764
|
+
maxPerPage?: number;
|
|
765
|
+
/** Show a page-size selector UI element. Default: false */
|
|
766
|
+
showPageSizeSelector?: boolean;
|
|
767
|
+
/** Options for the page-size selector. Default: [10, 20, 50, 100] */
|
|
768
|
+
pageSizeOptions?: number[];
|
|
769
|
+
/** Show first / last page buttons. Default: true */
|
|
770
|
+
showFirstLast?: boolean;
|
|
771
|
+
/** Show previous / next buttons. Default: true */
|
|
772
|
+
showPrevNext?: boolean;
|
|
773
|
+
/** Max visible page number buttons before ellipsis. Default: 7 */
|
|
774
|
+
maxVisible?: number;
|
|
775
|
+
/** Size variant for pagination button sizing. Default: "md" */
|
|
776
|
+
size?: "sm" | "md" | "lg";
|
|
777
|
+
}
|
|
778
|
+
declare const DEFAULT_PAGINATION_CONFIG: Required<PaginationConfig>;
|
|
779
|
+
/**
|
|
780
|
+
* Sticky header configuration for data tables.
|
|
781
|
+
* When `enabled` is true the thead is pinned while the tbody scrolls.
|
|
782
|
+
*/
|
|
783
|
+
interface StickyConfig {
|
|
784
|
+
/** Pin the header while the body scrolls. Default: false */
|
|
785
|
+
enabled?: boolean;
|
|
786
|
+
/** Tailwind `top-*` class applied to the sticky thead. Default: "top-0" */
|
|
787
|
+
topOffset?: string;
|
|
788
|
+
/** Max height of the scrollable container. Default: "600px" */
|
|
789
|
+
maxHeight?: string;
|
|
790
|
+
/** z-index of the sticky header. Default: 10 */
|
|
791
|
+
zIndex?: number;
|
|
792
|
+
}
|
|
793
|
+
declare const DEFAULT_STICKY_CONFIG: Required<StickyConfig>;
|
|
794
|
+
/** View mode options for DataTable. */
|
|
795
|
+
type TableViewMode = "table" | "grid" | "list";
|
|
796
|
+
/**
|
|
797
|
+
* Composable table configuration.
|
|
798
|
+
* Pass a Partial<TableConfig> to any DataTable / admin-list hook; values are
|
|
799
|
+
* deep-merged with DEFAULT_TABLE_CONFIG at the use site.
|
|
800
|
+
*/
|
|
801
|
+
interface TableConfig {
|
|
802
|
+
/** Items shown per page (internal pagination). Default: 20 */
|
|
803
|
+
pageSize?: number;
|
|
804
|
+
/** Sticky header behaviour. Default: disabled */
|
|
805
|
+
sticky?: StickyConfig;
|
|
806
|
+
/** Alternate row background colour. Default: false */
|
|
807
|
+
striped?: boolean;
|
|
808
|
+
/** Enable row checkbox selection. Default: false */
|
|
809
|
+
selectable?: boolean;
|
|
810
|
+
/** Show view-mode toggle (table / grid / list). Default: false */
|
|
811
|
+
showViewToggle?: boolean;
|
|
812
|
+
/** Default view mode. Default: "table" */
|
|
813
|
+
defaultViewMode?: TableViewMode;
|
|
814
|
+
/** Pagination widget configuration. */
|
|
815
|
+
pagination?: PaginationConfig;
|
|
816
|
+
}
|
|
817
|
+
declare const DEFAULT_TABLE_CONFIG: {
|
|
818
|
+
pageSize: number;
|
|
819
|
+
sticky: Required<StickyConfig>;
|
|
820
|
+
striped: boolean;
|
|
821
|
+
selectable: boolean;
|
|
822
|
+
showViewToggle: boolean;
|
|
823
|
+
defaultViewMode: TableViewMode;
|
|
824
|
+
pagination: Required<PaginationConfig>;
|
|
825
|
+
};
|
|
826
|
+
/**
|
|
827
|
+
* Utility: merge a Partial<TableConfig> over the defaults.
|
|
828
|
+
* Performs a shallow merge at the top level and deep merge for nested objects.
|
|
829
|
+
*/
|
|
830
|
+
declare function mergeTableConfig(override?: Partial<TableConfig>): typeof DEFAULT_TABLE_CONFIG;
|
|
831
|
+
|
|
832
|
+
export { type ApiRouteStub, type AuthPayload, type AuthUser, type ColumnExtensionOpts, type CreateShipmentInput, type CreateUserInput, DEFAULT_PAGINATION_CONFIG, DEFAULT_STICKY_CONFIG, DEFAULT_TABLE_CONFIG, type EmailAttachment, type EmailOptions, type EmailResult, type EventHandler, type FeatureExtension, type FeatureManifest, type FeaturesConfig, type GenericListResponse, type IAuthProvider, type ICacheProvider, type IDbProvider, type IEmailProvider, type IEventBus, type IPaymentProvider, type IQueueProvider, type IReadRepository, type IRealtimeRepository, type IRepository, type ISearchProvider, type ISessionProvider, type IShippingProvider, type IStorageProvider, type IStyleAdapter, type IWriteRepository, type LayoutSlots, type NavItem, type PagedResult, type PaginationConfig, type PaymentCapture, type PaymentOrder, type ProviderRegistry, type QueueJob, type Refund, type RouteStub, type SearchHit, type SearchOptions, type SearchResult, type ServiceabilityResult, type Shipment, type ShippingAddress, type SieveQuery, type SiteConfig, type StickyConfig, type StorageFile, type SuggestOptions, type TableColumn, type TableConfig, type TableViewMode, type TrackingEvent, type TrackingInfo, type UploadOptions, type WhereOp, type WithTransformOpts, _resetProviders, getProviders, mergeTableConfig, registerProviders };
|