sermon-js 0.1.0 → 0.2.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/README.md CHANGED
@@ -230,7 +230,7 @@ hooks and helpers to build your own. See [UI kit](docs/ui.md) and
230
230
  - [x] Host-agnostic scheduling: `tick()`, `startScheduler`, `/tick`, refresh on read
231
231
  - [x] HTTP API handler, Node adapter
232
232
  - [x] Stores: memory, Upstash REST, any Redis client
233
- - [x] Presets: Fly apps (sleep-safe, per region), GCP instances, AWS/S3, Statuspage, Pusher, AI/translation/video/email providers, MongoDB, Redis
233
+ - [x] Presets: Fly apps (sleep-safe, per region), GCP instances, AWS/S3, Statuspage, Pusher, AI/translation/video/email/search providers, Vercel, MongoDB, Redis, PostgreSQL
234
234
  - [x] Stores: SQL (Postgres, MySQL/MariaDB, SQLite), MongoDB
235
235
  - [x] React UI kit (headless hooks + HeroUI components)
236
236
  - [ ] First npm release
@@ -348,6 +348,16 @@ var redisPing = chunkXHXEKB3Q_cjs.definePreset()({
348
348
  })
349
349
  });
350
350
  var redis = { ping: redisPing };
351
+ var postgresPing = chunkXHXEKB3Q_cjs.definePreset()({
352
+ kind: "postgres.ping",
353
+ requiredFields: ["query"],
354
+ defaults: { slowMs: 1e3 },
355
+ derive: () => ({ name: "PostgreSQL", description: "Runs `select 1`" }),
356
+ check: ({ options, measure }) => measure(async () => {
357
+ await options.query("select 1");
358
+ })
359
+ });
360
+ var postgres = { ping: postgresPing };
351
361
 
352
362
  // src/presets/providers/shared.ts
353
363
  var bearer = (token) => ({ Authorization: `Bearer ${token}` });
@@ -466,6 +476,38 @@ var cambai = chunkXHXEKB3Q_cjs.definePreset()({
466
476
  derive: () => ({ name: "CAMB.AI", identity: "cambai", description: "Lists source languages" }),
467
477
  check: ({ secrets, http }) => call(http, "https://client.camb.ai/apis/source-languages", { headers: { "x-api-key": secrets.apiKey } })
468
478
  });
479
+ var elevenlabs = chunkXHXEKB3Q_cjs.definePreset()({
480
+ kind: "elevenlabs",
481
+ requiredSecrets: ["apiKey"],
482
+ derive: () => ({ name: "ElevenLabs", identity: "elevenlabs", description: "Reads the subscription" }),
483
+ check: async ({ options, secrets, http }) => {
484
+ const res = await http("https://api.elevenlabs.io/v1/user/subscription", {
485
+ headers: { "xi-api-key": secrets.apiKey }
486
+ });
487
+ const body = res.json ?? {};
488
+ if (res.status === 401 && body.detail?.status === "missing_permissions") {
489
+ return { status: "up", latencyMs: res.latencyMs, httpStatus: 401, metrics: { key: "restricted" } };
490
+ }
491
+ if (!res.ok) return res.outcome;
492
+ const metrics = body.tier ? { tier: body.tier } : void 0;
493
+ const { character_count: used, character_limit: limit } = body;
494
+ if (typeof used !== "number" || !limit) return { ...res.outcome, metrics };
495
+ return {
496
+ ...res.outcome,
497
+ ...quota({
498
+ label: "Credits",
499
+ used,
500
+ limit,
501
+ unit: "credits",
502
+ // ElevenLabs gives the reset as epoch seconds
503
+ resetsAt: body.next_character_count_reset_unix ? body.next_character_count_reset_unix * 1e3 : void 0,
504
+ warnAbovePct: 100 - (options.track?.quota?.warnBelowPct ?? 10),
505
+ warning: "Credits almost exhausted"
506
+ }),
507
+ metrics
508
+ };
509
+ }
510
+ });
469
511
 
470
512
  // src/presets/providers/translation.ts
471
513
  var deepl = chunkXHXEKB3Q_cjs.definePreset()({
@@ -697,6 +739,64 @@ var statuspage = chunkXHXEKB3Q_cjs.definePreset()({
697
739
  return { status, error: status === "up" ? void 0 : description };
698
740
  })
699
741
  });
742
+ var resend = chunkXHXEKB3Q_cjs.definePreset()({
743
+ kind: "resend",
744
+ requiredSecrets: ["apiKey"],
745
+ derive: () => ({ name: "Resend", identity: "resend", description: "Lists domains" }),
746
+ check: async ({ secrets, http }) => {
747
+ const res = await http("https://api.resend.com/domains", { headers: bearer(secrets.apiKey) });
748
+ const body = res.json ?? {};
749
+ if (res.status === 401 && body.name === "restricted_api_key") {
750
+ return { status: "up", latencyMs: res.latencyMs, httpStatus: 401, metrics: { key: "sending only" } };
751
+ }
752
+ if (!res.ok || !Array.isArray(body.data)) return res.outcome;
753
+ const verified = body.data.filter((domain) => domain.status === "verified").length;
754
+ const unverified = body.data.length > 0 && verified === 0;
755
+ return {
756
+ ...res.outcome,
757
+ status: unverified ? "degraded" : "up",
758
+ error: unverified ? "No verified domain" : void 0,
759
+ metrics: { domains: body.data.length, verified }
760
+ };
761
+ }
762
+ });
763
+ var meilisearch = chunkXHXEKB3Q_cjs.definePreset()({
764
+ kind: "meilisearch",
765
+ requiredFields: ["host"],
766
+ requiredSecrets: ["apiKey"],
767
+ derive: (options) => ({
768
+ name: "Meilisearch",
769
+ identity: options.index ? `meilisearch:${options.index}` : "meilisearch",
770
+ description: options.index ? `Reads the health and the ${options.index} index` : "Reads the health and lists indexes"
771
+ }),
772
+ check: async ({ options, secrets, http, combine }) => {
773
+ const host = options.host.replace(/\/+$/, "");
774
+ const headers = bearer(secrets.apiKey);
775
+ const index = options.index ? `/indexes/${encodeURIComponent(options.index)}` : void 0;
776
+ const health = call(
777
+ http,
778
+ `${host}/health`,
779
+ void 0,
780
+ (res2) => res2.json?.status === "available" ? void 0 : { status: "down", error: "Not available" }
781
+ );
782
+ const access = http(`${host}${index ?? "/indexes?limit=1"}`, { headers }).then((res2) => {
783
+ if (res2.status !== 404 || !options.index) return res2.outcome;
784
+ return {
785
+ status: "degraded",
786
+ latencyMs: res2.latencyMs,
787
+ httpStatus: 404,
788
+ error: `Index ${options.index} not found`
789
+ };
790
+ });
791
+ const stats = http(`${host}${index ?? ""}/stats`, { headers });
792
+ const outcome = combine(await Promise.all([health, access]));
793
+ const res = await stats;
794
+ if (!res.ok) return outcome;
795
+ const body = res.json ?? {};
796
+ const documents = body.numberOfDocuments ?? Object.values(body.indexes ?? {}).reduce((sum, entry) => sum + (entry.numberOfDocuments ?? 0), 0);
797
+ return { ...outcome, metrics: { ...outcome.metrics, documents, ...body.isIndexing ? { indexing: "yes" } : {} } };
798
+ }
799
+ });
700
800
 
701
801
  // src/presets/providers/pusher.ts
702
802
  var MAX_CHANNELS = 500;
@@ -828,6 +928,46 @@ var pusher = chunkXHXEKB3Q_cjs.definePreset()({
828
928
  }
829
929
  });
830
930
 
931
+ // src/presets/providers/vercel.ts
932
+ var aiGateway = chunkXHXEKB3Q_cjs.definePreset()({
933
+ kind: "vercel.aiGateway",
934
+ requiredSecrets: ["apiKey"],
935
+ derive: (options) => ({
936
+ name: "Vercel AI Gateway",
937
+ identity: "vercel.aiGateway",
938
+ description: options.track?.credits === false ? "Lists models" : "Reads the credit balance"
939
+ }),
940
+ check: ({ options, secrets, http }) => {
941
+ const headers = bearer(secrets.apiKey);
942
+ if (options.track?.credits === false) return call(http, "https://ai-gateway.vercel.sh/v1/models", { headers });
943
+ const warnBelow = options.track?.credits?.warnBelow ?? 5;
944
+ return call(http, "https://ai-gateway.vercel.sh/v1/credits", { headers }, (res) => {
945
+ const body = res.json ?? {};
946
+ const balance = Number(body.balance);
947
+ if (body.balance === void 0 || !Number.isFinite(balance)) return void 0;
948
+ const used = Number(body.total_used);
949
+ const low = balance < warnBelow;
950
+ return {
951
+ status: low ? "degraded" : "up",
952
+ error: low ? "Low credit balance" : void 0,
953
+ metrics: {
954
+ balance: `${round(balance, 2)} USD`,
955
+ ...body.total_used !== void 0 && Number.isFinite(used) ? { used: `${round(used, 2)} USD` } : {}
956
+ }
957
+ };
958
+ });
959
+ }
960
+ });
961
+ var blob = chunkXHXEKB3Q_cjs.definePreset()({
962
+ kind: "vercel.blob",
963
+ requiredSecrets: ["token"],
964
+ derive: () => ({ name: "Vercel Blob", identity: "vercel.blob", description: "Lists one blob of the store" }),
965
+ check: ({ secrets, http }) => call(http, "https://vercel.com/api/blob?limit=1", {
966
+ headers: { ...bearer(secrets.token), "x-api-version": "12" }
967
+ })
968
+ });
969
+ var vercel = { aiGateway, blob };
970
+
831
971
  Object.defineProperty(exports, "generic", {
832
972
  enumerable: true,
833
973
  get: function () { return chunkELV4ZU6G_cjs.generic; }
@@ -843,6 +983,7 @@ exports.cloudflareStream = cloudflareStream;
843
983
  exports.crashReason = crashReason;
844
984
  exports.deepgram = deepgram;
845
985
  exports.deepl = deepl;
986
+ exports.elevenlabs = elevenlabs;
846
987
  exports.fly = fly;
847
988
  exports.gcp = gcp;
848
989
  exports.gemini = gemini;
@@ -851,17 +992,21 @@ exports.gladia = gladia;
851
992
  exports.googleServiceAccountToken = googleServiceAccountToken;
852
993
  exports.googleTranslate = googleTranslate;
853
994
  exports.mailersend = mailersend;
995
+ exports.meilisearch = meilisearch;
854
996
  exports.mongodb = mongodb;
855
997
  exports.mux = mux;
856
998
  exports.openai = openai;
857
999
  exports.parseServiceAccount = parseServiceAccount;
1000
+ exports.postgres = postgres;
858
1001
  exports.pusher = pusher;
859
1002
  exports.redis = redis;
1003
+ exports.resend = resend;
860
1004
  exports.revolut = revolut;
861
1005
  exports.signGoogleAssertion = signGoogleAssertion;
862
1006
  exports.signPusherRequest = signPusherRequest;
863
1007
  exports.speechmatics = speechmatics;
864
1008
  exports.statuspage = statuspage;
865
1009
  exports.upstashRedis = upstashRedis;
1010
+ exports.vercel = vercel;
866
1011
  //# sourceMappingURL=index.cjs.map
867
1012
  //# sourceMappingURL=index.cjs.map