Αναφορά

TypeScript SDK

Το @citron/sdk είναι ένας μικρός client χωρίς εξαρτήσεις κατά την εκτέλεση. Λειτουργεί σε Node 18.17+, σε περιβάλλοντα edge και στον browser, και διατίθεται ως ESM και CommonJS.

Εγκατάσταση#

Το SDK δεν είναι ακόμη διαθέσιμο στο npm. Το στέλνουμε ως αρχείο πακέτου: επικοινωνήστε μαζί μας και θα το μοιραστούμε με την ομάδα σας.

bash
# Not on npm yet: install the package tarball we send you
npm install ./citron-sdk-0.1.0.tgz
ΕισαγωγήΤι περιέχει
@citron/sdkcreateClient, τα σφάλματα, τους τύπους, το generateTypes και τις βοηθητικές συναρτήσεις για webhook.
@citron/sdk/webhooksverifyWebhook, constructWebhookEvent, signWebhookBody και τίποτε άλλο, για όσο το δυνατόν μικρότερα πακέτα edge.
@citron/sdk/nextcitronTags και createRevalidateHandler για το App Router του Next.js.

Δημιουργία client#

lib/citron.ts
import { createClient } from '@citron/sdk';

export const citron = createClient({
  baseUrl: 'https://api.console.citronos.com', // "…/v1" is accepted too
  apiKey: process.env.CITRON_API_KEY,          // ck_pub_… (content and GraphQL only)
  locale: 'bg',                                // flatten localized fields to Bulgarian
  richtext: 'html',                            // html | json | both (API default: both)
  orgSlug: 'your-org',                         // for contact, forms and booking helpers
  timeoutMs: 10_000,                           // per attempt
  retry: { retries: 2, baseDelayMs: 300, maxDelayMs: 10_000 }, // or false
  cache: false,                                // true / { ttlMs } for an in-memory ETag cache
});
ΕπιλογήΠροεπιλογή
baseUrlυποχρεωτικόΗ προέλευση (origin) του API. Γίνεται δεκτή και η κατάληξη /v1.
apiKeyκαμίαck_pub_… ή ck_prev_…. Απαιτείται για περιεχόμενο και GraphQL, όχι για επικοινωνία, φόρμες ή κρατήσεις.
localeκαμίαΕπιστρέφει τα μεταφρασμένα πεδία σε αυτή τη γλώσσα σε κάθε κλήση περιεχομένου.
previewfalseΔιαβάζει πρόχειρα (status=draft). Απαιτεί κλειδί ck_prev_· με κλειδί ck_pub_ ο client αρνείται να ξεκινήσει.
richtextπροεπιλογή του API (both)html, json ή both.
orgSlugκαμίαΤο slug του χώρου εργασίας για τις βοηθητικές συναρτήσεις επικοινωνίας και κρατήσεων.
timeoutMs10000Ανά προσπάθεια· το 0 το απενεργοποιεί.
retry{ retries: 2, baseDelayMs: 300, maxDelayMs: 10000 }Ή false.
cachefalseΤο true επανεπικυρώνει με ETag· το { ttlMs } επιπλέον εξυπηρετεί από τη μνήμη για αυτό το διάστημα.
headers, fetchOptions, fetchκαμίαΑποστέλλονται με κάθε αίτημα· το fetch αντικαθιστά το globalThis.fetch.

Ανάγνωση περιεχομένου#

ΜέθοδοςΚαλείΕπιστρέφει
collection(slug).list(options)GET /v1/content/{slug}{ data, meta, nextCursor }
collection(slug).get(slugOrId, options)GET /v1/content/{slug}/{slugOrId}Την εγγραφή· προκαλεί σφάλμα σε 404.
collection(slug).find(slugOrId, options)το ίδιοΤην εγγραφή ή null.
collection(slug).single(options)GET /v1/content/{slug}Το έγγραφο μιας συλλογής singleton.
collection(slug).all(options)όλες τις σελίδεςΈναν ασύγχρονο iterator των εγγραφών.

Οι επιλογές λίστας αντιστοιχούν στις παραμέτρους REST: limit → pageSize, page ή cursor, filter, sort, fields, populate, locale, richtext, preview, καθώς και rawLocales: true για να λαμβάνετε χάρτες { bg, en } ακόμη κι όταν ο client έχει ορισμένη γλώσσα. Κάθε κλήση δέχεται επίσης headers, signal και fetchOptions.

TypeScript
const { data, meta } = await citron.collection('products').list({
  locale: 'en',
  filter: {
    category: 'station',              // filter[category]=station
    capacity: { gte: 5 },             // filter[capacity][gte]=5
    code: { in: ['BIO-6', 'BIO-10'] }, // filter[code][in]=BIO-6,BIO-10
  },
  sort: '-publishedAt',               // or { field: 'publishedAt', order: 'desc' }
  page: 1,
  limit: 20,                          // sent as pageSize
});
console.log(meta.total, data.length);

Τύποι από τις συλλογές σας#

Η εντολή citron-sdk typegen διαβάζει τους ορισμούς των συλλογών που βλέπει το κλειδί σας και γράφει ένα αρχείο TypeScript. Περάστε τον χάρτη του στο createClient και ο μεταγλωττιστής θα ελέγχει τα slug των συλλογών, τα πεδία, τα φίλτρα και το populate.

bash
npx citron-sdk typegen \
  --base-url https://api.console.citronos.com \
  --api-key "$CITRON_API_KEY" \
  --org your-org \
  --out src/citron.generated.ts
ΣημαίαΜεταβλητή περιβάλλοντος
--base-urlCITRON_BASE_URLΗ προέλευση του API.
--api-keyCITRON_API_KEYΈνα κλειδί παράδοσης του χώρου εργασίας.
--orgCITRON_ORGΤο slug του χώρου εργασίας (το κλειδί πρέπει να ανήκει σε αυτόν).
--outΑρχείο εξόδου· το - τυπώνει στην τυπική έξοδο. Προεπιλογή citron.generated.ts.
--fromΔιαβάζει τους ορισμούς από αποθηκευμένο αρχείο JSON αντί για το API.
TypeScript
import { createClient } from '@citron/sdk';
import type { CitronCollections } from './citron.generated';

const citron = createClient<CitronCollections>({
  baseUrl: 'https://api.console.citronos.com',
  apiKey: process.env.CITRON_API_KEY,
  locale: 'en',
});

const { data } = await citron.collection('products').list({ filter: { category: 'station' } });
const name: string | null | undefined = data[0]?.data.name; // typed from the collection

// @ts-expect-error: a misspelt collection is a compile error
citron.collection('prodcts');

Κάθε συλλογή αποκτά έναν τύπο …Data (τα μεταφρασμένα πεδία σε μία γλώσσα, όπως τα λαμβάνετε με locale) και έναν τύπο …Raw (τα μεταφρασμένα πεδία ως CitronLocaleMap<T>, όπως τα λαμβάνετε με rawLocales: true). Οι ζωντανές (live) συλλογές παραλείπονται, αφού το API παράδοσης δεν τις εξυπηρετεί. Εκτελέστε ξανά την εντολή κάθε φορά που αλλάζει μια συλλογή.

GraphQL#

Το citron.graphql(query, variables, options) επιστρέφει το data και προκαλεί CitronGraphQLError όταν η απάντηση περιέχει errors. Το { persisted: true } στέλνει πρώτα το hash και το ίδιο το ερώτημα μόνο όταν το ζητήσει το API. Δείτε GraphQL.

TypeScript
// persisted: true does both steps for you and uses GET by default
const data = await citron.graphql<{ products: { data: { slug: string | null }[]; meta: { total: number } } }>(
  'query ($n: Int) { products(pageSize: $n) { data { slug } meta { total } } }',
  { n: 10 },
  { persisted: true },
);
console.log(data.products.meta.total);

Επικοινωνία, φόρμες και κρατήσεις#

ΜέθοδοςΚαλεί
submitContact(input, { turnstileToken, idempotencyKey, orgSlug })POST /v1/public/{orgSlug}/contacts
forms.get(key)GET /v1/forms/{key} (ποτέ από την κρυφή μνήμη)
forms.submit(key, data, { renderToken, turnstileToken, referrer, utm })POST /v1/forms/{key}/submissions
booking.types(), booking.slots(params), booking.book(input)/v1/public/{orgSlug}/appointment-types, …/appointments/slots, …/appointments

Κάθε εγγραφή δεδομένων στέλνει ένα Idempotency-Key, που δημιουργείται αυτόματα αν δεν δώσετε δικό σας· δώστε δικό σας για να διατηρείται και μετά από ανανέωση της σελίδας. Δείτε φόρμες, επικοινωνία και κρατήσεις.

TypeScript
const types = await citron.booking.types({ orgSlug: 'your-org' });
const slots = await citron.booking.slots(
  { typeId: types[0].id, mode: 'online', from: new Date(), to: new Date(Date.now() + 14 * 86_400_000) },
  { orgSlug: 'your-org' },
);

const appointment = await citron.booking.book(
  {
    contact: { name: 'Ivan Petrov', email: 'ivan@example.com', phone: '+359888000000' },
    appointmentTypeId: types[0].id,
    staffUserId: slots[0].staffUserId,
    mode: 'online',
    startsAt: slots[0].startsAt,
  },
  { idempotencyKey, orgSlug: 'your-org' },
);
console.log(appointment.id, appointment.status);

Webhook#

Το verifyWebhook(rawBody, signatureHeader, secret, { maxAgeSeconds }) επιστρέφει true ή false και συγκρίνει σε σταθερό χρόνο. Το constructWebhookEvent επαληθεύει και αναλύει σε ένα βήμα και προκαλεί WebhookVerificationError. Και τα δύο χρησιμοποιούν Web Crypto. Δείτε webhook.

TypeScript
import { constructWebhookEvent, WebhookVerificationError } from '@citron/sdk/webhooks';

// Any runtime with Request/Response: Node 18+, edge, Workers
export async function handleCitronWebhook(req: Request): Promise<Response> {
  const raw = await req.text(); // the RAW body: re-serialised JSON has different bytes
  try {
    const event = await constructWebhookEvent(raw, req.headers.get('x-citron-signature'), process.env.CITRON_WEBHOOK_SECRET!, {
      maxAgeSeconds: 24 * 3600, // optional; the signed "at" makes old deliveries refusable
    });
    console.log(event.event, event.collection, event.entry.slug);
    return new Response(null, { status: 204 });
  } catch (err) {
    if (err instanceof WebhookVerificationError) return new Response(null, { status: 401 });
    throw err;
  }
}

Σφάλματα, επαναλήψεις και χρονικά όρια#

Κάθε αποτυχία είναι ένα CitronError με status (0 όταν δεν υπήρξε απάντηση), code, message, requestId, details και retryAfter, καθώς και isNotFound και isRetryable. Οι υποκλάσεις είναι CitronNetworkError, CitronTimeoutError και CitronGraphQLError.

TypeScript
import { CitronGraphQLError, isCitronError } from '@citron/sdk';

try {
  await citron.collection('products').get('does-not-exist');
} catch (err) {
  if (err instanceof CitronGraphQLError) {
    console.error(err.errors, err.data); // GraphQL errors and partial data
  } else if (isCitronError(err)) {
    console.error(err.status, err.code, err.message, err.requestId, err.details);
    if (err.isNotFound) { /* render your 404 */ }
    if (err.isRetryable) { /* 0, 408, 429 or 5xx; err.retryAfter when the server said */ }
  } else {
    throw err;
  }
}
  • Οι επαναλήψεις καλύπτουν τα 408, 429, 500, 502, 503, 504 και τα σφάλματα δικτύου, με εκθετική αύξηση της αναμονής και τυχαία διακύμανση, για αιτήματα GET, GraphQL και εγγραφές που φέρουν Idempotency-Key. Ένα Retry-After μεγαλύτερο από το maxDelayMs προκαλεί αμέσως σφάλμα με συμπληρωμένο το retryAfter.
  • Οι δικοί κωδικοί του SDK: NETWORK_ERROR, TIMEOUT, CONFIG_ERROR, INVALID_CURSOR, INVALID_FILTER, INVALID_RESPONSE, GRAPHQL_ERROR. Οι κωδικοί του API αναφέρονται στη σελίδα σφάλματα.

App Router του Next.js#

Αφήστε την κρυφή μνήμη του SDK απενεργοποιημένη και αφήστε το Next.js να κάνει την προσωρινή αποθήκευση: περάστε fetchOptions: { next: { revalidate, tags } } σε κάθε κλήση, προσθέστε ετικέτες με citronTags και επανεπικυρώστε από ένα webhook.

lib/citron.ts
// lib/citron.ts
import { createClient } from '@citron/sdk';
import { citronTags } from '@citron/sdk/next';

export const citron = createClient({
  baseUrl: process.env.CITRON_BASE_URL!,
  apiKey: process.env.CITRON_API_KEY!,
  locale: 'bg',
  richtext: 'html',
});

export const getProduct = (slug: string) =>
  citron.collection('products').find(slug, {
    populate: ['related'],
    fetchOptions: { next: { revalidate: 3600, tags: citronTags('products', slug) } },
  });

Κλειδιά και περιβάλλοντα εκτέλεσης#

  • Κρατήστε τα κλειδιά σε μεταβλητές περιβάλλοντος του διακομιστή. Ένα κλειδί ck_pub_ διαβάζει μόνο δημοσιευμένο περιεχόμενο, αλλά το όριο αιτημάτων ισχύει ανά κλειδί, οπότε ένα κλειδί μέσα σε δημόσιο πακέτο επιτρέπει σε οποιονδήποτε να εξαντλήσει το όριό σας.
  • Μην στέλνετε ποτέ κλειδί ck_prev_ στον browser: διαβάζει όλα τα πρόχειρα.
  • Η επικοινωνία, οι φόρμες και οι κρατήσεις δεν απαιτούν κλειδί, οπότε αυτές οι βοηθητικές συναρτήσεις είναι ασφαλείς στον browser.
  • Σε περιβάλλον εκτέλεσης χωρίς καθολικό fetch, περάστε ένα στη ρύθμιση του client.

Αυτή η αναφορά καλύπτει μόνο το δημόσιο API και ακολουθεί τη σύμβαση συμβατότητας του API: οι δημόσιες αποκρίσεις μόνο αποκτούν νέα πεδία. Η περιγραφή OpenAPI αυτών των διαδρομών βρίσκεται στο /developers/openapi.json.