@marianmeres/ecsuite 1.3.2 → 2.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/AGENTS.md +134 -20
- package/API.md +108 -25
- package/README.md +127 -0
- package/dist/adapters/http/_http.d.ts +34 -0
- package/dist/adapters/http/_http.js +75 -0
- package/dist/adapters/http/cart.d.ts +21 -0
- package/dist/adapters/http/cart.js +52 -0
- package/dist/adapters/http/customer.d.ts +22 -0
- package/dist/adapters/http/customer.js +35 -0
- package/dist/adapters/http/mod.d.ts +21 -0
- package/dist/adapters/http/mod.js +20 -0
- package/dist/adapters/http/order.d.ts +24 -0
- package/dist/adapters/http/order.js +43 -0
- package/dist/adapters/http/payment.d.ts +32 -0
- package/dist/adapters/http/payment.js +77 -0
- package/dist/adapters/http/product.d.ts +18 -0
- package/dist/adapters/http/product.js +30 -0
- package/dist/adapters/http/wishlist.d.ts +19 -0
- package/dist/adapters/http/wishlist.js +42 -0
- package/dist/adapters/mock/cart.d.ts +4 -2
- package/dist/adapters/mock/cart.js +3 -7
- package/dist/adapters/mock/customer.d.ts +3 -1
- package/dist/adapters/mock/customer.js +3 -2
- package/dist/adapters/mock/order.d.ts +3 -1
- package/dist/adapters/mock/order.js +7 -5
- package/dist/adapters/mock/payment.d.ts +3 -1
- package/dist/adapters/mock/payment.js +34 -20
- package/dist/adapters/mock/product.d.ts +3 -1
- package/dist/adapters/mock/product.js +3 -1
- package/dist/adapters/mock/wishlist.d.ts +4 -2
- package/dist/adapters/mock/wishlist.js +3 -7
- package/dist/adapters/mod.d.ts +4 -1
- package/dist/adapters/mod.js +4 -1
- package/dist/domains/base.d.ts +13 -6
- package/dist/domains/base.js +31 -12
- package/dist/domains/cart.js +17 -0
- package/dist/domains/customer.js +18 -4
- package/dist/domains/order.d.ts +33 -15
- package/dist/domains/order.js +34 -20
- package/dist/domains/payment.js +16 -2
- package/dist/domains/product.d.ts +29 -40
- package/dist/domains/product.js +99 -81
- package/dist/domains/wishlist.js +4 -2
- package/dist/suite.d.ts +49 -1
- package/dist/suite.js +90 -8
- package/dist/types/adapter.d.ts +10 -7
- package/dist/types/events.d.ts +6 -2
- package/dist/types/state.d.ts +2 -0
- package/docs/future-improvements.md +116 -0
- package/package.json +15 -6
package/dist/suite.js
CHANGED
|
@@ -12,6 +12,15 @@ import { OrderManager } from "./domains/order.js";
|
|
|
12
12
|
import { CustomerManager } from "./domains/customer.js";
|
|
13
13
|
import { PaymentManager } from "./domains/payment.js";
|
|
14
14
|
import { ProductManager } from "./domains/product.js";
|
|
15
|
+
/** Combine multiple unsubscribers into one (also `Symbol.dispose`-compatible). */
|
|
16
|
+
function combineUnsubscribers(...unsubs) {
|
|
17
|
+
const fn = () => {
|
|
18
|
+
for (const u of unsubs)
|
|
19
|
+
u();
|
|
20
|
+
};
|
|
21
|
+
fn[Symbol.dispose] = fn;
|
|
22
|
+
return fn;
|
|
23
|
+
}
|
|
15
24
|
/**
|
|
16
25
|
* Main ECSuite class - orchestrates all e-commerce domain managers.
|
|
17
26
|
*
|
|
@@ -33,6 +42,21 @@ export class ECSuite {
|
|
|
33
42
|
#clog = createClog("ecsuite", { color: "auto" });
|
|
34
43
|
#pubsub;
|
|
35
44
|
#context;
|
|
45
|
+
#initDomains;
|
|
46
|
+
#autoResetOnIdentityChange;
|
|
47
|
+
/**
|
|
48
|
+
* Resolves when the most recent `initialize()` (auto or manual, including
|
|
49
|
+
* the one triggered by an identity switch) has settled. Always available;
|
|
50
|
+
* defaults to `Promise.resolve()` if `autoInitialize: false`.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```typescript
|
|
54
|
+
* const suite = createECSuite({ adapters: { cart } });
|
|
55
|
+
* await suite.ready; // wait for initial fetches
|
|
56
|
+
* await suite.cart.addItem(...);
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
ready = Promise.resolve();
|
|
36
60
|
/** Cart domain manager */
|
|
37
61
|
cart;
|
|
38
62
|
/** Wishlist domain manager */
|
|
@@ -52,6 +76,8 @@ export class ECSuite {
|
|
|
52
76
|
});
|
|
53
77
|
this.#pubsub = createPubSub();
|
|
54
78
|
this.#context = config.context ?? {};
|
|
79
|
+
this.#initDomains = config.initializeDomains;
|
|
80
|
+
this.#autoResetOnIdentityChange = config.autoResetOnIdentityChange !== false;
|
|
55
81
|
const storageType = config.storage?.type ?? "local";
|
|
56
82
|
// Initialize domain managers with shared pubsub
|
|
57
83
|
this.cart = new CartManager({
|
|
@@ -89,9 +115,10 @@ export class ECSuite {
|
|
|
89
115
|
pubsub: this.#pubsub,
|
|
90
116
|
cacheTtl: config.productCacheTtl,
|
|
91
117
|
});
|
|
92
|
-
// Auto-initialize if configured
|
|
118
|
+
// Auto-initialize if configured. `ready` exposes the in-flight promise
|
|
119
|
+
// so callers don't race the constructor.
|
|
93
120
|
if (config.autoInitialize !== false) {
|
|
94
|
-
this.initialize(
|
|
121
|
+
this.ready = this.initialize(this.#initDomains);
|
|
95
122
|
}
|
|
96
123
|
}
|
|
97
124
|
/** Initialize domains. When called without arguments, initializes all domains.
|
|
@@ -118,15 +145,55 @@ export class ECSuite {
|
|
|
118
145
|
"order",
|
|
119
146
|
"customer",
|
|
120
147
|
"payment",
|
|
148
|
+
"product",
|
|
121
149
|
];
|
|
122
|
-
const toInit = domains ?? all;
|
|
150
|
+
const toInit = domains ?? this.#initDomains ?? all;
|
|
151
|
+
// Remember the most recently initialized set so identity switches
|
|
152
|
+
// (and other re-inits) re-fetch the same domains.
|
|
153
|
+
this.#initDomains = toInit;
|
|
123
154
|
this.#clog.debug("initializing domains", toInit);
|
|
124
155
|
await Promise.all(toInit.map((name) => this[name].initialize()));
|
|
125
156
|
this.#clog.debug("domains initialized", toInit);
|
|
126
157
|
}
|
|
127
|
-
/**
|
|
158
|
+
/**
|
|
159
|
+
* Update context across all domains.
|
|
160
|
+
*
|
|
161
|
+
* If `customerId` transitions (including to/from undefined) and
|
|
162
|
+
* `autoResetOnIdentityChange` is enabled (default), this also resets
|
|
163
|
+
* all domains and re-initializes them — assigning the new in-flight
|
|
164
|
+
* promise to `ready`. Callers that need to wait for the new identity's
|
|
165
|
+
* data should `await suite.ready` afterwards (or use `switchIdentity`).
|
|
166
|
+
*/
|
|
128
167
|
setContext(context) {
|
|
129
168
|
this.#clog.debug("setContext", context);
|
|
169
|
+
const prevCustomerId = this.#context.customerId;
|
|
170
|
+
this.#context = { ...this.#context, ...context };
|
|
171
|
+
this.cart.setContext(context);
|
|
172
|
+
this.wishlist.setContext(context);
|
|
173
|
+
this.order.setContext(context);
|
|
174
|
+
this.customer.setContext(context);
|
|
175
|
+
this.payment.setContext(context);
|
|
176
|
+
this.product.setContext(context);
|
|
177
|
+
const newCustomerId = this.#context.customerId;
|
|
178
|
+
if (this.#autoResetOnIdentityChange && prevCustomerId !== newCustomerId) {
|
|
179
|
+
this.#clog.debug("identity changed; resetting domains", {
|
|
180
|
+
from: prevCustomerId,
|
|
181
|
+
to: newCustomerId,
|
|
182
|
+
});
|
|
183
|
+
this.reset();
|
|
184
|
+
this.ready = this.initialize(this.#initDomains);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Atomically switch to a new identity: merge context, reset all domains,
|
|
189
|
+
* and re-initialize. Returns a promise that settles when the re-init
|
|
190
|
+
* completes. Use this instead of `setContext` when you need to await
|
|
191
|
+
* the identity switch (also updates `suite.ready`).
|
|
192
|
+
*
|
|
193
|
+
* Works even when `autoResetOnIdentityChange: false`.
|
|
194
|
+
*/
|
|
195
|
+
async switchIdentity(context) {
|
|
196
|
+
this.#clog.debug("switchIdentity", context);
|
|
130
197
|
this.#context = { ...this.#context, ...context };
|
|
131
198
|
this.cart.setContext(context);
|
|
132
199
|
this.wishlist.setContext(context);
|
|
@@ -134,6 +201,9 @@ export class ECSuite {
|
|
|
134
201
|
this.customer.setContext(context);
|
|
135
202
|
this.payment.setContext(context);
|
|
136
203
|
this.product.setContext(context);
|
|
204
|
+
this.reset();
|
|
205
|
+
this.ready = this.initialize(this.#initDomains);
|
|
206
|
+
await this.ready;
|
|
137
207
|
}
|
|
138
208
|
/** Get the current context */
|
|
139
209
|
getContext() {
|
|
@@ -186,10 +256,7 @@ export class ECSuite {
|
|
|
186
256
|
error: event.error,
|
|
187
257
|
});
|
|
188
258
|
});
|
|
189
|
-
return ()
|
|
190
|
-
unsub1();
|
|
191
|
-
unsub2();
|
|
192
|
-
};
|
|
259
|
+
return combineUnsubscribers(unsub1, unsub2);
|
|
193
260
|
}
|
|
194
261
|
/** Reset all domains to initial state */
|
|
195
262
|
reset() {
|
|
@@ -201,6 +268,21 @@ export class ECSuite {
|
|
|
201
268
|
this.payment.reset();
|
|
202
269
|
this.product.clearCache();
|
|
203
270
|
}
|
|
271
|
+
/**
|
|
272
|
+
* Tear down the suite: unsubscribe all listeners on the internal pubsub
|
|
273
|
+
* and clear the product cache. Use when the consumer (e.g., a SPA
|
|
274
|
+
* lifecycle hook) is done with the suite — long-lived apps that recreate
|
|
275
|
+
* suites otherwise leak subscribers across instances.
|
|
276
|
+
*
|
|
277
|
+
* Persisted storage is intentionally NOT cleared (cart/wishlist data
|
|
278
|
+
* should survive page reloads); call `reset()` first if you also want
|
|
279
|
+
* to wipe in-memory state.
|
|
280
|
+
*/
|
|
281
|
+
destroy() {
|
|
282
|
+
this.#clog.debug("destroy");
|
|
283
|
+
this.product.clearCache();
|
|
284
|
+
this.#pubsub.unsubscribeAll();
|
|
285
|
+
}
|
|
204
286
|
}
|
|
205
287
|
/**
|
|
206
288
|
* Factory function to create an ECSuite instance.
|
package/dist/types/adapter.d.ts
CHANGED
|
@@ -18,8 +18,6 @@ export interface CartAdapter {
|
|
|
18
18
|
removeItem(productId: UUID, ctx: DomainContext): Promise<CartData>;
|
|
19
19
|
/** Clear all items */
|
|
20
20
|
clear(ctx: DomainContext): Promise<CartData>;
|
|
21
|
-
/** Sync full cart state (for optimistic update reconciliation) */
|
|
22
|
-
sync(cart: CartData, ctx: DomainContext): Promise<CartData>;
|
|
23
21
|
}
|
|
24
22
|
/** Wishlist adapter interface */
|
|
25
23
|
export interface WishlistAdapter {
|
|
@@ -31,18 +29,23 @@ export interface WishlistAdapter {
|
|
|
31
29
|
removeItem(productId: UUID, ctx: DomainContext): Promise<WishlistData>;
|
|
32
30
|
/** Clear all items */
|
|
33
31
|
clear(ctx: DomainContext): Promise<WishlistData>;
|
|
34
|
-
/** Sync full wishlist state */
|
|
35
|
-
sync(wishlist: WishlistData, ctx: DomainContext): Promise<WishlistData>;
|
|
36
32
|
}
|
|
37
33
|
/** Order create payload (status is set by server) */
|
|
38
34
|
export type OrderCreatePayload = Omit<OrderData, "status">;
|
|
39
35
|
export type { OrderCreateResult } from "@marianmeres/collection-types";
|
|
40
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* Order adapter interface (read + create).
|
|
38
|
+
*
|
|
39
|
+
* All read/create methods return `OrderCreateResult` (`{ model_id, data }`)
|
|
40
|
+
* so the manager can identify orders by their server-assigned `model_id`.
|
|
41
|
+
* Returning bare `OrderData` previously made dedup/update impossible because
|
|
42
|
+
* `OrderData` has no `model_id` field — only an open index signature.
|
|
43
|
+
*/
|
|
41
44
|
export interface OrderAdapter {
|
|
42
45
|
/** Fetch all orders for customer */
|
|
43
|
-
fetchAll(ctx: DomainContext): Promise<
|
|
46
|
+
fetchAll(ctx: DomainContext): Promise<OrderCreateResult[]>;
|
|
44
47
|
/** Fetch single order by ID */
|
|
45
|
-
fetchOne(orderId: UUID, ctx: DomainContext): Promise<
|
|
48
|
+
fetchOne(orderId: UUID, ctx: DomainContext): Promise<OrderCreateResult>;
|
|
46
49
|
/** Create new order — returns data + model_id */
|
|
47
50
|
create(order: OrderCreatePayload, ctx: DomainContext): Promise<OrderCreateResult>;
|
|
48
51
|
}
|
package/dist/types/events.d.ts
CHANGED
|
@@ -8,8 +8,12 @@ import type { UUID } from "@marianmeres/collection-types";
|
|
|
8
8
|
import type { DomainError, DomainState } from "./state.js";
|
|
9
9
|
/** Domain identifiers */
|
|
10
10
|
export type DomainName = "cart" | "wishlist" | "order" | "customer" | "payment" | "product";
|
|
11
|
-
/**
|
|
12
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Domain names that support `initialize()`. As of the unified ProductManager,
|
|
13
|
+
* every domain implements initialize() — for product it is a no-op that
|
|
14
|
+
* transitions to "ready" so consumers can rely on the same readiness contract.
|
|
15
|
+
*/
|
|
16
|
+
export type InitializableDomainName = DomainName;
|
|
13
17
|
/** Event types emitted by the suite */
|
|
14
18
|
export type ECSuiteEventType = "domain:state:changed" | "domain:error" | "domain:synced" | "cart:item:added" | "cart:item:updated" | "cart:item:removed" | "cart:cleared" | "wishlist:item:added" | "wishlist:item:removed" | "wishlist:cleared" | "order:created" | "order:fetched" | "customer:updated" | "customer:fetched" | "payment:fetched" | "payment:initiated" | "payment:captured" | "product:fetched";
|
|
15
19
|
/** Base event data */
|
package/dist/types/state.d.ts
CHANGED
|
@@ -35,6 +35,8 @@ export interface DomainContext {
|
|
|
35
35
|
customerId?: UUID;
|
|
36
36
|
/** Optional session ID */
|
|
37
37
|
sessionId?: UUID;
|
|
38
|
+
/** Optional JWT forwarded to HTTP adapters as `Authorization: Bearer <jwt>`. */
|
|
39
|
+
jwt?: string;
|
|
38
40
|
/** Additional context properties for adapter-specific needs */
|
|
39
41
|
[key: string]: unknown;
|
|
40
42
|
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Ecsuite — Future Improvements
|
|
2
|
+
|
|
3
|
+
A design review of the current client-side data-flow model, with gaps to consider as usage grows. Not a commitment — a backlog for discussion.
|
|
4
|
+
|
|
5
|
+
Companion to `@marianmeres/ownsuite`'s `docs/future-improvements.md`. Both suites share `BaseDomainManager`, so most structural items transfer; this document highlights what applies, what doesn't, and what is ecsuite-specific.
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
Ecsuite is a **fixed-domain e-commerce state manager** (cart, wishlist, order, customer, payment, product). Unlike ownsuite's generic owner-scoped CRUD, each manager has domain-specific state shape, persistence, and operations. The design is intentionally closed — a known, curated set of domains — which makes "pluggability" a non-goal.
|
|
10
|
+
|
|
11
|
+
## Where the current design aligns with best practice
|
|
12
|
+
|
|
13
|
+
- Store-per-domain FSM exposed as a Svelte store.
|
|
14
|
+
- Optimistic updates with rollback on cart/wishlist/customer.
|
|
15
|
+
- Optimistic `create` for cart items with client-assigned temp id, swapped on server response (correct pattern).
|
|
16
|
+
- `localStorage` persistence for cart/wishlist survives reload without a round-trip.
|
|
17
|
+
- ProductManager uses a TTL cache to avoid redundant fetches.
|
|
18
|
+
- Transport-agnostic adapter boundary per domain; full mock coverage for tests.
|
|
19
|
+
- Typed pubsub event bus scoped per domain (`cart:*`, `order:*`, etc.).
|
|
20
|
+
|
|
21
|
+
## Gaps inherited from the shared base
|
|
22
|
+
|
|
23
|
+
### 1. No request deduplication or in-flight cancellation
|
|
24
|
+
|
|
25
|
+
Any domain's `refresh()` can be called twice quickly (route remount, focus event) and produce parallel fetches with a last-write-wins race.
|
|
26
|
+
|
|
27
|
+
**Possible direction:** track an in-flight promise per operation key; coalesce concurrent callers.
|
|
28
|
+
|
|
29
|
+
### 2. No `AbortSignal` plumbing through adapters
|
|
30
|
+
|
|
31
|
+
Component unmount cannot cancel in-flight fetches; results land in a detached store and may overwrite fresher data.
|
|
32
|
+
|
|
33
|
+
**Possible direction:** thread `AbortSignal` through adapter method signatures; abort on supersession and disposal.
|
|
34
|
+
|
|
35
|
+
### 3. No staleness / revalidation policy (except ProductManager)
|
|
36
|
+
|
|
37
|
+
`lastSyncedAt` is tracked but unused on cart/order/customer/payment. No focus/reconnect refetch, no cross-tab sync. ProductManager's TTL is a one-off — no shared cache primitive.
|
|
38
|
+
|
|
39
|
+
**Possible direction:** lift ProductManager's TTL approach into a reusable base-level option; add optional focus/reconnect listeners behind a flag.
|
|
40
|
+
|
|
41
|
+
### 4. No per-row pending state during optimistic updates — **acute here**
|
|
42
|
+
|
|
43
|
+
More important in ecsuite than ownsuite: cart UIs routinely need "this line item is updating" spinners. Currently, modifying one cart item flips the whole cart to `syncing`, so consumers can't show a per-line spinner without their own bookkeeping.
|
|
44
|
+
|
|
45
|
+
**Possible direction:** track pending item ids in state; expose as a `Set<itemId>` alongside `items`.
|
|
46
|
+
|
|
47
|
+
### 5. Error state couples to data availability
|
|
48
|
+
|
|
49
|
+
One failed `update` puts the whole domain in `error`, even though `data` is still valid. Consumers must remember to ignore `state === "error"` when `data` exists.
|
|
50
|
+
|
|
51
|
+
**Possible direction:** decouple — keep `state` at `ready`, surface the last error as a sibling signal (`lastError`).
|
|
52
|
+
|
|
53
|
+
### 6. `initialize()` swallows errors
|
|
54
|
+
|
|
55
|
+
Silent boot failure unless you subscribe to `*:error` events.
|
|
56
|
+
|
|
57
|
+
**Possible direction:** add `suite.hasErrors()` / `suite.getErrors()` helpers.
|
|
58
|
+
|
|
59
|
+
### 7. Whole-list rollback snapshot
|
|
60
|
+
|
|
61
|
+
Less critical than in ownsuite — carts/wishlists are small. Flag for later.
|
|
62
|
+
|
|
63
|
+
## Gaps that apply to specific domains only
|
|
64
|
+
|
|
65
|
+
### 8. Query-keyed cache — OrderManager (and partly ProductManager)
|
|
66
|
+
|
|
67
|
+
Order history is naturally filtered/paginated (status, date range). A single `orders` slot can't hold multiple views concurrently. Cart/wishlist/customer/payment are session-singletons and don't need this.
|
|
68
|
+
|
|
69
|
+
**Possible direction:** upgrade OrderManager to a `queries: Map<queryKey, {...}>` shape; leave singleton managers alone.
|
|
70
|
+
|
|
71
|
+
### 9. Pagination / infinite-scroll primitives — OrderManager, ProductManager
|
|
72
|
+
|
|
73
|
+
`meta` is opaque; cursor/offset merging is consumer-implemented. Needed for order history and product listings.
|
|
74
|
+
|
|
75
|
+
**Possible direction:** first-class `loadMore()` on those managers with a pluggable merge strategy.
|
|
76
|
+
|
|
77
|
+
## Ecsuite-specific concerns (no ownsuite analogue)
|
|
78
|
+
|
|
79
|
+
### 10. localStorage persistence — multiple latent issues
|
|
80
|
+
|
|
81
|
+
- **No cross-tab sync.** Two tabs mutating the cart diverge until one wins on reload. A `storage` event listener would mirror changes.
|
|
82
|
+
- **No schema versioning / migration.** If cart item shape changes between releases, old persisted carts will either crash deserialization or silently corrupt state. A `version` field + migration hook is standard.
|
|
83
|
+
- **No encryption or redaction.** Carts can contain semi-sensitive data (product ids a user browses privately, promo codes). Probably acceptable for e-commerce norms, but worth an explicit decision.
|
|
84
|
+
- **No quota handling.** `localStorage.setItem` can throw on quota-exceeded; currently unhandled.
|
|
85
|
+
|
|
86
|
+
### 11. Temp-id → real-id swap on optimistic cart `create`
|
|
87
|
+
|
|
88
|
+
Known-hazardous pattern. Risks:
|
|
89
|
+
|
|
90
|
+
- Events fired with the temp id before swap — subscribers caching by id see a ghost.
|
|
91
|
+
- Subsequent `update`/`delete` called on a temp id while the server response is in flight — needs either queuing or id remapping.
|
|
92
|
+
- External references (URLs, analytics) captured against a temp id become stale.
|
|
93
|
+
|
|
94
|
+
**Possible direction:** document the swap contract explicitly; consider buffering mutations until the real id arrives; emit a dedicated `cart:item:id-reconciled` event so subscribers can rekey.
|
|
95
|
+
|
|
96
|
+
### 12. ProductManager TTL cache is a one-off
|
|
97
|
+
|
|
98
|
+
Useful, but not reusable. CustomerManager and OrderManager could benefit from the same primitive. See #3.
|
|
99
|
+
|
|
100
|
+
## Items that do NOT apply
|
|
101
|
+
|
|
102
|
+
- **Pluggable manager types** — ecsuite is intentionally closed (fixed 6 domains). This is a design contract, not a flaw. (Applies to ownsuite only.)
|
|
103
|
+
|
|
104
|
+
## Prioritization sketch
|
|
105
|
+
|
|
106
|
+
Rough order if tackled:
|
|
107
|
+
|
|
108
|
+
1. **#4 per-row pending on CartManager** — highest user-visible value; UIs want it now.
|
|
109
|
+
2. **#2 AbortSignal** — cheap correctness win.
|
|
110
|
+
3. **#1 dedup** — cheap, removes a class of races.
|
|
111
|
+
4. **#5 decouple error from state** — small API change, big DX win.
|
|
112
|
+
5. **#10 localStorage cross-tab sync + versioning** — bite-sized, prevents real bugs.
|
|
113
|
+
6. **#11 temp-id swap hardening** — audit existing code; document or fix concretely.
|
|
114
|
+
7. **#3 shared TTL/staleness primitive** — lift from ProductManager.
|
|
115
|
+
8. **#8 + #9 query cache & pagination for OrderManager** — defer until a concrete consumer needs it; design together.
|
|
116
|
+
9. Remaining items — opportunistic.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@marianmeres/ecsuite",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/mod.js",
|
|
6
6
|
"types": "dist/mod.d.ts",
|
|
@@ -10,14 +10,23 @@
|
|
|
10
10
|
"import": "./dist/mod.js"
|
|
11
11
|
}
|
|
12
12
|
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"LICENSE",
|
|
16
|
+
"README.md",
|
|
17
|
+
"API.md",
|
|
18
|
+
"AGENTS.md",
|
|
19
|
+
"CLAUDE.md",
|
|
20
|
+
"docs"
|
|
21
|
+
],
|
|
13
22
|
"author": "Marian Meres",
|
|
14
23
|
"license": "MIT",
|
|
15
24
|
"dependencies": {
|
|
16
|
-
"@marianmeres/clog": "^3.
|
|
17
|
-
"@marianmeres/collection-types": "^1.
|
|
18
|
-
"@marianmeres/http-utils": "^2.
|
|
19
|
-
"@marianmeres/pubsub": "^
|
|
20
|
-
"@marianmeres/store": "^
|
|
25
|
+
"@marianmeres/clog": "^3.17.1",
|
|
26
|
+
"@marianmeres/collection-types": "^1.37.0",
|
|
27
|
+
"@marianmeres/http-utils": "^2.6.0",
|
|
28
|
+
"@marianmeres/pubsub": "^3.0.0",
|
|
29
|
+
"@marianmeres/store": "^3.0.1"
|
|
21
30
|
},
|
|
22
31
|
"repository": {
|
|
23
32
|
"type": "git",
|