Collection
The Collection<T> class represents a reactive group of items in the ReactiveModel system. It handles loading,
filtering, and managing multiple instances of a defined Item, ensuring synchronization with a shared registry and
support for dynamic updates from external data sources.
This class is designed to work with domain-specific Item models and their corresponding ICollectionProvider,
enabling reactive collections that automatically reflect backend or external changes.
🧩 Inheritance
- Extends: - Model<Collection<T, P>>
- Integrates with: - Item<T>,- RegistryFactory, and- ICollectionProvider
🧪 Basic Usage
💡 To better understand how providers work and how to implement them, see the providers documentation.
import { Collection } from '@beyond-js/reactive/entities/collection';
import { User } from './user'; // Must extend Item
import { UserProvider } from './user-provider';
class Users extends Collection<User, UserProvider> {
	constructor() {
		super({
			entity: 'users',
			provider: UserProvider,
			item: User,
		});
	}
}
---
## 📦 Constructor
```ts
new Collection({
  entity: string;
  provider?: class implements ICollectionProvider;
  item: class extends Item;
  nextParamName?: string; // Optional: name of the pagination parameter (default: "next")
})
```
### Parameters
-   `entity`: **(required)** Identifier for the collection type
-   `provider`: **(optional)** Data provider for remote operations
-   `item`: **(required)** The `Item` class to instantiate on data load
-   `nextParamName`: **(optional)** Name of the pagination parameter sent to the provider. Defaults to `"next"`. Use this if your provider expects a different key (e.g., `"cursor"`, `"start"`).
---
## 🔑 Key Properties
| Property   | Type                  | Description                                        |
| ---------- | --------------------- | -------------------------------------------------- |
| `entity`   | `string`              | Name of the collection’s data type                 |
| `Item`     | `typeof Item<T>`      | Reference to the `Item` class                      |
| `provider` | `ICollectionProvider` | Optional data source with `list()` and `publish()` |
| `items`    | `T[]`                 | Current items in the collection                    |
| `map`      | `Map<ItemId, T>`      | Internal map of items by ID                        |
---
## 📡 Events
| Event           | Triggered When                           |
| --------------- | ---------------------------------------- |
| `load`          | After successful load and initialization |
| `items.changed` | When items are added or removed          |
| `change`        | On any mutation to collection state      |
---
## 📖 Pagination
### Internal Pagination Handling
- The collection now manages pagination using an internal cursor (by default, the `next` property).
- The parameter name for the cursor sent to the provider can be customized using the `nextParamName` option in the constructor.
- When loading more data (pagination), simply call `load({ update: true })`. The collection will send the current cursor value to the provider using the configured parameter name.
- The provider should return either an array or an object with `{ items, next, total }`.
- The `next` (or custom) value will be updated automatically after each load.
- The `getNext()` method returns the current cursor value, and `getTotal()` returns the total number of available items (if provided).
### Example: Custom Pagination Parameter
```ts
class Users extends Collection<User, UserProvider> {
  constructor() {
    super({
      entity: 'users',
      provider: UserProvider,
      item: User,
      nextParamName: 'start', // Use 'start' as the pagination parameter
    });
  }
}
```
### Example: Loading More Data
```ts
// Initial load
await users.load({ limit: 20 });
// Load next page (uses internal cursor)
await users.load({ update: true });
```
### Example: Provider Response
```ts
// Provider may return:
{
  items: [...], // Array of items for this page
  next: 'nextCursor', // Cursor for the next page (or null if no more pages)
  total: 100 // (optional) Total number of items available
}
```
### Migration Notes
- If you previously passed the pagination cursor manually in `load`, update your code to rely on the internal handling.
- Use `nextParamName` if your API expects a different cursor parameter name.
---
## ⚙️ Methods
### `async load(args?: ILoadSpecs<T>): Promise<T[]>`
Loads items from the provider using optional filtering, ordering, and pagination arguments.
```ts
await users.load({
	where: { status: { equals: 'active' } },
	orderBy: { name: 'asc' },
	limit: 20,
});
// Pagination is handled internally: you do not need to pass the pagination cursor.
// If the collection has a pagination cursor (e.g., `next` or as configured by `nextParamName`), it will be sent automatically in the request.
// The provider can return either an array or an object with `{ items, next, total }`.
// To fetch the next page, call `load({ update: true })` and the collection will use the latest cursor.
```
> 💡 **Note**: The filters used in the `where` clause are fully customizable.
> The documented structure (e.g., `equals`, `in`, `gt`, etc.) is a recommendation for compatibility with common
> databases like IndexedDB, SQL, or MongoDB.
> You are free to define any structure as long as your provider knows how to interpret it.
### Recommended Operators
-   `equals`, `not`, `in`, `notIn`
-   `contains`, `startsWith`, `endsWith`
-   `gt`, `gte`, `lt`, `lte`
---
### `setItems(data: any[], clear?: boolean): void`
Adds items to the collection from raw data.
If `clear` is true, replaces all current items.
---
### `getProperties(): { items: T[] }`
Returns an object containing the collection's current items.
---
### `getItemProperties(): object[]`
Returns a plain array of property snapshots (`getProperties()`) from each item.
---
### `set(data: Partial<T>): any`
Overrides `Model.set()` to update collection-level properties and emit `change`.
---
## 🔁 Registry Integration
This collection listens to `RegistryFactory` events:
-   `record.published`: Adds matching items to the collection if they match filters.
-   `record.deleted`: Removes deleted items from the collection.
These events ensure reactive updates across shared registry state.
---
## 🧠 Filtering with `matchesFilters`
Before adding new registries to the collection, they are tested against the defined filters from the latest `load()`
call.
Supports logical `AND` and `OR` conditions, as well as nested filters.
> 🔎 Filters are matched in-memory using the `matchesFilters()` logic after `load()`, so consistent formatting between
> your filters and your provider implementation is encouraged.
---
## 🧩 Example: Collection with Filtered Sync
```ts
await users.load({
	where: {
		status: { equals: 'active' },
		age: { gte: 18 },
	},
});
// When a new user is published matching this filter,
// it will be added automatically to the collection.
```
---
## 🔌 Provider Interface
The optional provider should implement:
```ts
interface ICollectionProvider {
	list(specs?: any): Promise<any>;
	load?(specs?: any): Promise<any>;
	publish?(data: any): Promise<any>;
	remove?(specs?: any): Promise<any>;
}
```
---
## 📄 License
MIT © BeyondJSLast updated