khotan-data 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
@@ -4,6 +4,14 @@ Data primitives for TypeScript — ETL pipelines, transforms, and Drizzle Postgr
4
4
 
5
5
  Built for **Next.js + Drizzle + Postgres** projects. Think better-auth for data management.
6
6
 
7
+ ## Install
8
+
9
+ ```bash
10
+ npm i khotan-data
11
+ ```
12
+
13
+ Requires `drizzle-orm` as a peer dependency (you almost certainly already have it).
14
+
7
15
  ## CLI
8
16
 
9
17
  Scaffold components into your Next.js + Drizzle project:
@@ -17,6 +25,7 @@ npx khotan init --full
17
25
 
18
26
  # Add components (reusable building blocks — never create pages)
19
27
  npx khotan add schema # Drizzle table definitions (plugs, flows, runs, resources, mappings)
28
+ npx khotan add cache # Durable key/value caches for workflows and relays
20
29
  npx khotan add plug # Fetch wrapper with auth, retry, pagination
21
30
  npx khotan add inflow # Workflow-backed flow for pulling data in
22
31
  npx khotan add outflow # Workflow-backed flow for pushing data out
@@ -33,18 +42,27 @@ npx khotan add hub --yes # Auto-accept dependency install prompts
33
42
 
34
43
  ## Factory (Runtime Engine)
35
44
 
36
- Register plugs, flows, and resources — the factory upserts them on boot and serves a REST API:
45
+ Register plugs, caches, flows, and resources — the factory upserts them on boot and serves a REST API:
37
46
 
38
47
  ```typescript
39
48
  import { khotan, drizzleAdapter, toNextJsHandler } from "khotan-data/factory";
40
49
  import { db } from "@/db";
41
50
  import { shopifyPlug } from "@/lib/khotan/plugs/shopify";
42
51
  import { shopifyProductsInflow } from "@/lib/khotan/flows/shopify-products";
52
+ import { shopifyProductsSnapshotCache } from "@/lib/khotan/caches/shopify-products-snapshot";
43
53
 
44
54
  const khotanData = khotan({
45
55
  adapter: drizzleAdapter(db),
56
+ // Gate the management API behind your auth layer (see "Security" below).
57
+ authorize: async (request) => {
58
+ const session = await auth.api.getSession({ headers: request.headers });
59
+ return Boolean(session?.user);
60
+ },
46
61
  resources: [
47
- { name: "products", connectField: "sku" },
62
+ { name: "products", mapping: { connectField: "sku" } },
63
+ ],
64
+ caches: [
65
+ shopifyProductsSnapshotCache,
48
66
  ],
49
67
  plugs: [
50
68
  {
@@ -66,13 +84,78 @@ await khotanData.flow("products-inflow", { plugName: "shopify" }).start({
66
84
  });
67
85
  ```
68
86
 
69
- ## Install
87
+ ## Security
70
88
 
71
- ```bash
72
- npm install khotan-data
89
+ The management API (`/api/khotan/*`) exposes plug credentials and operational
90
+ controls. It is **public unless you gate it**. Pass an `authorize` hook — it
91
+ receives the raw `Request` and returns `true`/`false`, so it composes directly
92
+ with session libraries like better-auth:
93
+
94
+ ```typescript
95
+ authorize: async (request) => {
96
+ const session = await auth.api.getSession({ headers: request.headers });
97
+ return session?.user?.role === "admin";
98
+ },
73
99
  ```
74
100
 
75
- Requires `drizzle-orm` as a peer dependency (you almost certainly already have it).
101
+ - `KHOTAN_SECRET` encrypts plug credentials **at rest** (AES-256-GCM). It is not
102
+ an auth credential — it never gates requests. Set it to a high-entropy value.
103
+ - Inbound webhooks (verified via per-plug `onVerify`), the cron dispatcher
104
+ (`CRON_SECRET`), and debug routes (`KHOTAN_DEBUG`, non-production only) are
105
+ exempt from `authorize` automatically.
106
+ - `KHOTAN_DEBUG` is force-disabled when `NODE_ENV=production`. The cron route
107
+ fails closed in production when `CRON_SECRET` is unset.
108
+ - Protect the Hub dashboard page (e.g. `/config`) with your app's middleware —
109
+ `authorize` only guards the API.
110
+
111
+ ## Caches
112
+
113
+ Use first-class caches when a flow, relay, catch, or pass needs durable state between runs.
114
+
115
+ ```typescript
116
+ import { cache } from "@/lib/khotan/caches/cache";
117
+
118
+ export const shopifyProductsSnapshotCache = cache({
119
+ name: "shopify-products-snapshot",
120
+ scope: {
121
+ plug: "shopify",
122
+ resource: "products",
123
+ flow: "shopify-products-inflow",
124
+ },
125
+ ttl: "6h",
126
+ });
127
+ ```
128
+
129
+ Inside workflows, use `khotanCache(ctx, "name")` for snapshots, cursors, and dedupe markers:
130
+
131
+ ```typescript
132
+ import { khotanCache } from "khotan-data/factory";
133
+
134
+ async function shopifyProductsWorkflow(ctx: InflowContext) {
135
+ "use workflow";
136
+
137
+ async function syncProducts() {
138
+ "use step";
139
+ const snapshotCache = khotanCache(ctx, "shopify-products-snapshot");
140
+ const previous =
141
+ (await snapshotCache.get<Array<Record<string, unknown>>>("latest")) ?? [];
142
+
143
+ const response = await shopifyPlug.get<{ data?: Array<Record<string, unknown>> }>("/products");
144
+ const records = Array.isArray(response.data) ? response.data : [];
145
+
146
+ await snapshotCache.set("latest", records);
147
+
148
+ return {
149
+ extracted: records.length,
150
+ transformed: records.length,
151
+ created: records.length,
152
+ metadata: { previousCount: previous.length },
153
+ };
154
+ }
155
+
156
+ return syncProducts();
157
+ }
158
+ ```
76
159
 
77
160
  ## Quick Start
78
161