Αναφορά
TypeScript SDK
Το @citron/sdk είναι ένας μικρός client χωρίς εξαρτήσεις κατά την εκτέλεση. Λειτουργεί σε Node 18.17+, σε περιβάλλοντα edge και στον browser, και διατίθεται ως ESM και CommonJS.
Εγκατάσταση#
Το SDK δεν είναι ακόμη διαθέσιμο στο npm. Το στέλνουμε ως αρχείο πακέτου: επικοινωνήστε μαζί μας και θα το μοιραστούμε με την ομάδα σας.
# Not on npm yet: install the package tarball we send you
npm install ./citron-sdk-0.1.0.tgz| Εισαγωγή | Τι περιέχει |
|---|---|
@citron/sdk | createClient, τα σφάλματα, τους τύπους, το generateTypes και τις βοηθητικές συναρτήσεις για webhook. |
@citron/sdk/webhooks | verifyWebhook, constructWebhookEvent, signWebhookBody και τίποτε άλλο, για όσο το δυνατόν μικρότερα πακέτα edge. |
@citron/sdk/next | citronTags και createRevalidateHandler για το App Router του Next.js. |
Δημιουργία client#
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 | καμία | Επιστρέφει τα μεταφρασμένα πεδία σε αυτή τη γλώσσα σε κάθε κλήση περιεχομένου. |
preview | false | Διαβάζει πρόχειρα (status=draft). Απαιτεί κλειδί ck_prev_· με κλειδί ck_pub_ ο client αρνείται να ξεκινήσει. |
richtext | προεπιλογή του API (both) | html, json ή both. |
orgSlug | καμία | Το slug του χώρου εργασίας για τις βοηθητικές συναρτήσεις επικοινωνίας και κρατήσεων. |
timeoutMs | 10000 | Ανά προσπάθεια· το 0 το απενεργοποιεί. |
retry | { retries: 2, baseDelayMs: 300, maxDelayMs: 10000 } | Ή false. |
cache | false | Το 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.
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.
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-url | CITRON_BASE_URL | Η προέλευση του API. |
--api-key | CITRON_API_KEY | Ένα κλειδί παράδοσης του χώρου εργασίας. |
--org | CITRON_ORG | Το slug του χώρου εργασίας (το κλειδί πρέπει να ανήκει σε αυτόν). |
--out | Αρχείο εξόδου· το - τυπώνει στην τυπική έξοδο. Προεπιλογή citron.generated.ts. | |
--from | Διαβάζει τους ορισμούς από αποθηκευμένο αρχείο JSON αντί για το API. |
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.
// 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, που δημιουργείται αυτόματα αν δεν δώσετε δικό σας· δώστε δικό σας για να διατηρείται και μετά από ανανέωση της σελίδας. Δείτε φόρμες, επικοινωνία και κρατήσεις.
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.
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.
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
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.