# Sync your system into collections

Your Met answers better when it knows your business. A **collection** is a workspace table
the Met reads and writes on its own: mirror your catalog, your inventory or your price list
there, and stop writing an endpoint for every question someone might ask.

This recipe syncs an external system into a collection, nightly, without duplicating
anything.

## Before you start

Your key needs `collections:write`, `items:read` and `items:write`.

## 1. Create the collection and its fields

Once. Each field's `name` is the **key** the value lives under inside the item and never
changes; the `label` is what shows on screen and you can change it.

```ts
import Met from '@meteor.ia/sdk';

const met = new Met(process.env.MET_API_KEY!, {
  workspaceId: Number(process.env.MET_WORKSPACE_ID),
});

const catalog = await met.collections.create({ name: 'Catalog' });

for (const field of [
  { name: 'sku',    label: 'SKU',    type: 'text',     required: true },
  { name: 'name',   label: 'Name',   type: 'text',     required: true },
  { name: 'price',  label: 'Price',  type: 'currency' },
  { name: 'stock',  label: 'Stock',  type: 'number' },
  { name: 'active', label: 'Active', type: 'boolean' },
] as const) {
  await met.collections.addField(catalog.id, field);
}
```

The available types are `text`, `number`, `currency`, `email`, `date`, `datetime`,
`boolean`, `url`, `select`, `prompt`, `usuario`, `contact`, `page`, `sitio`, `image` and
`file`.

## 2. Sync without duplicating

The collection won't enforce uniqueness for you. The pattern that works is to pull what's
already there, index it by your own business key — the `sku` here — and decide between
create and update.

```ts
// What already lives in Meteor, indexed by SKU.
const bySku = new Map<string, number>();
for await (const item of met.items.iterate(catalog.id)) {
  bySku.set(item.data.sku, item.id);
}

for (const product of await pullFromYourSystem()) {
  const existing = bySku.get(product.sku);

  if (!existing) {
    await met.items.create(catalog.id, {
      sku: product.sku,
      name: product.name,
      price: product.price,
      stock: product.stock,
      active: true,
    });
    continue;
  }

  // Only what moves often: one patch per field avoids stomping on
  // whatever someone edited by hand in the dashboard.
  await met.items.patchField(catalog.id, existing, 'price', product.price);
  await met.items.patchField(catalog.id, existing, 'stock', product.stock);
  bySku.delete(product.sku);
}

// Whatever is left in the map is gone from your system.
for (const [, itemId] of bySku) {
  await met.items.patchField(catalog.id, itemId, 'active', false);
}
```

Notice that what disappeared gets marked `active: false` instead of deleted. A deleted item
takes with it every reference other Mets or tasks made to it; an inactive one can be
filtered out and still explains why an old conversation talks about it.

## 3. Check the Met is seeing it

```ts
const found = await met.items.search('blue bike');
```

`search` looks across the content of the workspace's items. If your Met says "I don't have
that information" and this does find it, the data isn't the problem: the Met doesn't have
the collection among its own. You set that per Met — see
[Mets and their tools](mets-and-tools.html).

## Running it nightly

```bash
met items list --collection 12 --limit 5   # quick check from the terminal
```

Any cron works. If you already use GitHub Actions, the
[run Meteor from CI](recipe-github-actions.html) recipe has the workflow ready.

## Next

- Views, folders and the full model: [Data: collections and items](collections-and-items.html).
- If what you want to mirror is people rather than products, it belongs in the CRM and not
  in a collection: [CRM and contacts](contacts.html).
