@gemmein/sdk 0.6.0 → 0.7.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/CHANGELOG.md +77 -0
- package/REFERENCE.md +174 -9
- package/dist/index.cjs +204 -2
- package/dist/index.d.cts +130 -5
- package/dist/index.d.ts +130 -5
- package/dist/index.js +201 -1
- package/llms.txt +143 -17
- package/package.json +1 -1
package/llms.txt
CHANGED
|
@@ -39,7 +39,10 @@ creating no accounts is the correct behavior until the verdict is in.
|
|
|
39
39
|
safety rules (private, shared, admin_write, public_read, community,
|
|
40
40
|
addressed, direct), subscriptions and one-off digital products (Stripe
|
|
41
41
|
built in; any provider that signs its webhooks drives access through a
|
|
42
|
-
relay),
|
|
42
|
+
relay), consumable credits (buy a pack, spend atomically, a zero
|
|
43
|
+
floor — see Credits), a chat call to OpenAI, Anthropic or Google on
|
|
44
|
+
the founder's own key at one credit per call (see The AI route),
|
|
45
|
+
image uploads and paid file delivery, entitlement-gated
|
|
43
46
|
access, race-safe bookings/stock/counters, account-deletion cascade.
|
|
44
47
|
Out of scope — each of these downgrades the verdict and must never be
|
|
45
48
|
silently approximated:
|
|
@@ -50,9 +53,10 @@ creating no accounts is the correct behavior until the verdict is in.
|
|
|
50
53
|
nothing in between — so an app whose CORE loop needs teams, roles
|
|
51
54
|
or seats DOESN'T FIT
|
|
52
55
|
- carts, quantities, physical goods, shipping, inventory
|
|
53
|
-
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
- usage-based INVOICING (a bill after the fact, by token or by minute):
|
|
57
|
+
not shipped. Credits ARE — consumable credits: buy a pack, spend
|
|
58
|
+
atomically, a zero floor — see Credits; a "100 credit pack" is a
|
|
59
|
+
product with Grants credits = 100, and a model call is one credit
|
|
56
60
|
- seats (owner-run: not coming)
|
|
57
61
|
- hosted functions or cron: Gemmein runs no code of yours — but your
|
|
58
62
|
own function anywhere asks Gemmein who a person is and what they
|
|
@@ -391,14 +395,15 @@ contents.
|
|
|
391
395
|
what the person holds NOW: `access` (keys like "access:pro"), `grants`
|
|
392
396
|
(each with its source KIND only — subscription | purchase | manual |
|
|
393
397
|
trial | promotion | migration | relay — plus start and expiry), and `credits`,
|
|
394
|
-
|
|
395
|
-
|
|
398
|
+
`{ balance }` — how many credits the person holds now (engine 0.8.0 and
|
|
399
|
+
later; `null` from an older local engine). Never subscription status, amounts
|
|
396
400
|
or Stripe ids: gate on what a person HOLDS, never on billing. Person
|
|
397
401
|
id, NEVER an email address. verifySession needs nothing extra; looking
|
|
398
402
|
someone up by id and granting are new power, so they sit behind per-key
|
|
399
403
|
checkboxes the HUMAN ticks when minting the key — ask your human to
|
|
400
404
|
tick "Look up a person's access by id" / "Grant and revoke access" /
|
|
401
|
-
"Create a person by email before they sign in" (invitePerson)
|
|
405
|
+
"Create a person by email before they sign in" (invitePerson) /
|
|
406
|
+
"Spend a person's credits" (spendCredits). A
|
|
402
407
|
key grants MANUAL access only (manual | trial | promotion | migration):
|
|
403
408
|
purchases and subscriptions come from the built-in Stripe path, and
|
|
404
409
|
another provider's payment grants through a relay (source `relay`). Every /server/*
|
|
@@ -442,17 +447,20 @@ contents.
|
|
|
442
447
|
writes the receipt, tells the person, passes the signal to your URL.
|
|
443
448
|
Stripe stays built in; any provider that signs its webhooks drives
|
|
444
449
|
access the same way.
|
|
445
|
-
Does: write a record · grant access · revoke access ·
|
|
446
|
-
the event is about · call your URL (a signed notice to
|
|
450
|
+
Does: write a record · grant access · revoke access · grant credits ·
|
|
451
|
+
email the person the event is about · call your URL (a signed notice to
|
|
452
|
+
a server YOU run).
|
|
447
453
|
Does not: run your code; call OpenAI or any third API for you (call_url
|
|
448
454
|
carries Gemmein's body and signature, never your key or a request you
|
|
449
455
|
shape); attach a person to a schedule (no "email everyone due tomorrow"
|
|
450
456
|
yet); transform or compute data; act on more than one person per event
|
|
451
|
-
(a list is a broadcast);
|
|
457
|
+
(a list is a broadcast); spend credits (a spend is your server's or the
|
|
458
|
+
AI route's — a relay only adds).
|
|
452
459
|
Needs something else when: the work THINKS — a model call, a score, a
|
|
453
460
|
transform, a third API — put that in your own function behind call_url;
|
|
454
461
|
it receives a verified event with the person already resolved. The AI
|
|
455
|
-
route
|
|
462
|
+
route is where Gemmein holds your provider key and makes a chat call for
|
|
463
|
+
a signed-in person at one credit each — see The AI route.
|
|
456
464
|
Use a relay when: a provider other than Stripe signs a webhook that
|
|
457
465
|
should change access or write a record (GoCardless, Paddle, Lemon
|
|
458
466
|
Squeezy, a form or signature tool); a record change should tell the
|
|
@@ -504,6 +512,8 @@ contents.
|
|
|
504
512
|
- grant_access { entitlement, expiresAt?, reason? } — source `relay`, the
|
|
505
513
|
seventh grant source; "granted by relay <name>" on the person's page.
|
|
506
514
|
- revoke_access { entitlement } — ends every live grant of it.
|
|
515
|
+
- grant_credits { amount (1..10,000), reason? } — adds to the person's
|
|
516
|
+
balance, once per event; the ledger line reads "relay: <reason>".
|
|
507
517
|
- email_person { subject, text, kind? } — rides notify's caps.
|
|
508
518
|
- call_url { url } — https, signed POST (`X-Gemmein-Signature`), 10 s; the
|
|
509
519
|
same `id` on every retry and replay — your URL deduplicates on it.
|
|
@@ -675,11 +685,10 @@ contents.
|
|
|
675
685
|
screen and send them to checkout; retry only after they hold one. Proof surfaces: `await g.purchases.mine()`
|
|
676
686
|
(everything they paid for, refunds applied, with the `grants` each purchase
|
|
677
687
|
carries) and `await g.subscriptions.mine()`.
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
first). NO seats, by design — not coming.
|
|
688
|
+
Access is yes-or-no. A QUANTITY beside it is credits — buy a pack, spend
|
|
689
|
+
atomically, a zero floor (see Credits): a "100 credit pack" is a product
|
|
690
|
+
with Grants credits = 100, and the ledger ADDS at every purchase, so a
|
|
691
|
+
second pack makes 200. NO seats, by design — not coming.
|
|
683
692
|
- Two FAMILIES of grant, and the line between them is law. Every grant is
|
|
684
693
|
one key + one reason + a start + maybe an end, never edited (an extension
|
|
685
694
|
is a NEW grant). PURCHASE-TIED (`subscription`, `purchase`): written only
|
|
@@ -740,6 +749,122 @@ contents.
|
|
|
740
749
|
webhook. NO carts, NO quantities — one product per checkout by design; a
|
|
741
750
|
cart is N checkouts or one bundled product. 404 unknown_product lists
|
|
742
751
|
what the app actually sells — use those names.
|
|
752
|
+
- Credits — a quantity beside access. See https://docs.gemmein.com/credits
|
|
753
|
+
What it is: a balance your customers hold and your product spends — a
|
|
754
|
+
pack they buy, a comp you give, a call that costs one.
|
|
755
|
+
Does: a product grants credits at purchase (its Grants credits field);
|
|
756
|
+
your server spends them with a reason; the AI route spends one per call;
|
|
757
|
+
a relay grants them; the owner's dashboard shows the balance and the
|
|
758
|
+
ledger and comps by hand. Every movement is one ledger line.
|
|
759
|
+
Does not: expire; go negative (floor 0 — a spend past the balance is
|
|
760
|
+
refused whole, never partly); price by token; open access (that is an
|
|
761
|
+
entitlement — a person with 500 credits and no plan is still refused by
|
|
762
|
+
a locked collection); count seats or quotas; spend from the browser.
|
|
763
|
+
Needs something else when: access should switch on with a plan (an
|
|
764
|
+
entitlement — see Paid ACCESS); you bill by usage after the fact (not
|
|
765
|
+
shipped — sell packs up front); the spend must happen in the browser
|
|
766
|
+
(it never does: your server, a relay or the AI route moves credits).
|
|
767
|
+
The case: "sell a 100-credit pack and let each chat cost one". The pack
|
|
768
|
+
is a product on the Payments page with Grants credits = 100 (the
|
|
769
|
+
product's `grantsCredits`); each purchase ADDS 100 to the buyer's
|
|
770
|
+
balance (two packs make 200), and a FULL refund claws back what is still
|
|
771
|
+
unspent, floor 0. The app reads the balance to show it:
|
|
772
|
+
|
|
773
|
+
const { balance } = await g.credits.balance() // browser, signed in
|
|
774
|
+
|
|
775
|
+
Your server spends by person id, with a reason and a key, so a retry
|
|
776
|
+
never spends twice (the key needs "Spend a person's credits" ticked):
|
|
777
|
+
|
|
778
|
+
const r = await gemmeinServer(sk).spendCredits(person.id, {
|
|
779
|
+
amount: 1, reason: "export", key: `export:${jobId}`
|
|
780
|
+
})
|
|
781
|
+
// { ok: true, spent: 1, deduped: false, balance: { before: 12, after: 11 },
|
|
782
|
+
// event: { id: "cev_…", reason: "export", actor: "<the key's name>" } }
|
|
783
|
+
// the same key again: spent: 0, deduped: true, the same event, nothing moved
|
|
784
|
+
// 402 credits_exhausted — "this person has 0 credits — the spend needs 1"
|
|
785
|
+
|
|
786
|
+
`holdings.credits` is `{ balance }` on verifySession and holdings, so one
|
|
787
|
+
verify answers who, what they hold AND how many. Numbers: no expiry ·
|
|
788
|
+
floor 0 · a balance carries at most 1,000,000,000 · a pack 1..1,000,000 ·
|
|
789
|
+
a server spend 1..10,000 per call · a comp ≤100,000 · a relay grant
|
|
790
|
+
1..10,000 · spend lines kept 90 days on the cloud rail (purchases, grants
|
|
791
|
+
and clawbacks are kept). Facts: a purchase by an email that has never
|
|
792
|
+
signed in creates the person and credits them; your `key` is scoped to
|
|
793
|
+
the person and namespaced (`srv:`), so one order id reused for two people
|
|
794
|
+
charges both and rotating the secret key never re-charges; a full refund
|
|
795
|
+
claws back what THAT purchase's ledger row granted, whatever the product
|
|
796
|
+
says today; the owner's page lists the newest 50 non-spend lines and the
|
|
797
|
+
credits the person spent in the last 30 days. Codes: `credits_exhausted`
|
|
798
|
+
(402 — carries the balance; show the pack, never retry the spend) ·
|
|
799
|
+
`capability_required` (403) · `person_not_found` (404) · `invalid_amount`
|
|
800
|
+
/ `invalid_reason` / `invalid_key` (400) · `dedupe_conflict` (409 — the
|
|
801
|
+
key already names a different movement) · `credits_ceiling` (409 — the
|
|
802
|
+
balance would pass 1,000,000,000; nothing added).
|
|
803
|
+
- The AI route — your app talks to OpenAI, Anthropic or Google through
|
|
804
|
+
Gemmein, on YOUR provider key, which never reaches the browser.
|
|
805
|
+
See https://docs.gemmein.com/ai
|
|
806
|
+
What it is: your human pastes their provider key once in the dashboard's
|
|
807
|
+
Keys room (write-only: never shown again, never in the app's code); a
|
|
808
|
+
signed-in person's chat request reaches the provider through Gemmein and
|
|
809
|
+
costs one credit.
|
|
810
|
+
Does: takes the provider's OWN request body from a signed-in person,
|
|
811
|
+
spends one credit, forwards the body as sent — minus the `provider`
|
|
812
|
+
field, and for Google minus `model` and `stream`, which ride the URL —
|
|
813
|
+
with the key, content-type and accept added, passes the provider's
|
|
814
|
+
status and bytes straight back (a stream stays a stream), and refunds
|
|
815
|
+
the credit only when the provider fails before its first byte.
|
|
816
|
+
Does not: choose models, cache, summarise, moderate, or reshape the
|
|
817
|
+
request or the answer; run from a server key (`sk_` is refused); reach
|
|
818
|
+
any host but the three; price by token (one credit per call, whatever
|
|
819
|
+
the length); refund a call that dies mid-stream, or one the caller
|
|
820
|
+
abandons before the headers (hanging up early does not refund).
|
|
821
|
+
Needs something else when: your SERVER makes the model call (call the
|
|
822
|
+
provider directly with your key; the gate still answers who they are and
|
|
823
|
+
what they hold); the call is not a chat call — embeddings, images, audio
|
|
824
|
+
(the provider directly, from your server); the provider is not on the
|
|
825
|
+
list (write to hello@gemmein.com).
|
|
826
|
+
The case: "a chat app where each message costs one credit and my OpenAI
|
|
827
|
+
key stays private". The body is exactly what the provider documents for
|
|
828
|
+
its chat endpoint; `g.ai.chat` returns the fetch Response untouched:
|
|
829
|
+
|
|
830
|
+
const res = await g.ai.chat({
|
|
831
|
+
model: "gpt-4o-mini", stream: true,
|
|
832
|
+
messages: [{ role: "user", content: text }]
|
|
833
|
+
}, { provider: "openai" }) // provider is optional when one key is set
|
|
834
|
+
// res.status and res.body are the provider's own (SSE stays SSE) —
|
|
835
|
+
// a provider 4xx/5xx comes back the same way: read res.ok; only
|
|
836
|
+
// Gemmein's own refusals throw
|
|
837
|
+
// res.headers: x-gemmein-credits-remaining: 41
|
|
838
|
+
// x-gemmein-credit: refunded (a failure before the first byte)
|
|
839
|
+
|
|
840
|
+
For a non-stream answer as one string, whichever provider answered (a
|
|
841
|
+
provider non-2xx throws `provider_error` with its status and message):
|
|
842
|
+
|
|
843
|
+
const answer = await g.ai.text({ model: "gpt-4o-mini",
|
|
844
|
+
messages: [{ role: "user", content: text }] })
|
|
845
|
+
|
|
846
|
+
Branch on `err.code`: credits_exhausted (402 — the message carries the
|
|
847
|
+
balance; show the pack) · ai_not_configured (409 — no key set; the owner
|
|
848
|
+
pastes one) · provider_required (400 — more than one key set; name
|
|
849
|
+
`provider`) · model_not_allowed (403 — the owner's allowlist names what
|
|
850
|
+
is allowed) · ai_capped (429 — 20 per person per minute; wait for
|
|
851
|
+
`resetAt`) · payload_too_large (413 — 256 KB) · invalid_body (400 — the
|
|
852
|
+
body must be the provider's JSON object, nested at most 32 levels) ·
|
|
853
|
+
session_required (401 — sign in first) · scope_denied (403 — a server
|
|
854
|
+
key; the route is for the browser) · provider_unreachable (502 — no
|
|
855
|
+
answer before the first byte; the credit is refunded; retry) ·
|
|
856
|
+
provider_error (`g.ai.text` only — the provider's own non-2xx, its
|
|
857
|
+
status and message). Numbers: 1 credit per call · 20/min/person · 256 KB
|
|
858
|
+
· 170 s in all, and on a stream 10 s to the first response headers.
|
|
859
|
+
Facts: `?provider=` and `?stream=1` on the URL do what the body fields
|
|
860
|
+
do; the owner may list up to 20 allowed models (the Keys room's test
|
|
861
|
+
call uses the first); every `/ai/chat` call counts toward the app's
|
|
862
|
+
api_requests band like any other request; a provider that echoes the key
|
|
863
|
+
in a refusal reaches you as `***<hint>`. The owner's Usage room counts
|
|
864
|
+
the calls; the provider bills the tokens on the owner's own account.
|
|
865
|
+
`gemmein dev` answers a fake provider without a key (header
|
|
866
|
+
`x-gemmein-ai: fake`), so the loop runs locally; set GEMMEIN_AI_KEY_OPENAI
|
|
867
|
+
/ _ANTHROPIC / _GOOGLE there for a real call.
|
|
743
868
|
- Drafts on PUBLIC collections (public_read, community): create with the
|
|
744
869
|
OPTION `{ published: false }` → hidden from every reader except its
|
|
745
870
|
author and the owner, server-enforced; publish with
|
|
@@ -789,7 +914,8 @@ contents.
|
|
|
789
914
|
conversation and customer replies land; holding the billing band;
|
|
790
915
|
running a relay: an inbound webhook from any provider that
|
|
791
916
|
signs its calls, a schedule, or a record change → write a record,
|
|
792
|
-
grant or revoke access, email the person, or call your
|
|
917
|
+
grant or revoke access, grant credits, email the person, or call your
|
|
918
|
+
URL, with
|
|
793
919
|
retries and a replay button (the definition is a file you write —
|
|
794
920
|
see Relays — and the dashboard is where it is read, paused,
|
|
795
921
|
replayed and its secrets rotated). None
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gemmein/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Gemmein SDK \u2014 passwordless auth, safe storage, and Stripe-driven record flips for AI-built apps. Small enough that one prompt teaches the whole API.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|