@kirkelliott/zap 0.1.11 → 0.1.13

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
@@ -2,24 +2,23 @@
2
2
 
3
3
  > Drop a `.zap` file in S3. It becomes an API endpoint.
4
4
 
5
- Like PHP but JavaScript, serverless, and free on AWS forever.
5
+ You already know S3. You've been dropping files in buckets for years. This makes those files executable.
6
6
 
7
- One Lambda function runs permanently as the runtime. When a request arrives at `/proxy`, it fetches `proxy.zap` from your S3 bucket, evaluates it, and returns the response. To change behavior, upload a new file. No redeploy. No CI. No infra changes.
7
+ Drop `hello.zap` GET `/hello` returns a response. Change the file behavior changes instantly. No redeploy. No CI. No config.
8
8
 
9
9
  ---
10
10
 
11
- ## Quick start
11
+ ## Setup
12
12
 
13
13
  ```bash
14
- git clone https://github.com/dmvjs/s3node && cd s3node
15
- npm install
16
- npm run init
14
+ mkdir my-project && cd my-project
15
+ npx @kirkelliott/zap init
17
16
  ```
18
17
 
19
- `init` provisions the full AWS stack and prints your endpoint URL. Requires AWS credentials (`aws configure`). Takes about 30 seconds.
18
+ This provisions everything on your AWS account and prints a URL. Takes about 30 seconds. Requires AWS credentials — run `aws configure` first if you haven't.
20
19
 
21
20
  ```
22
- building runtime
21
+ packaging runtime
23
22
  creating bucket ✓ zap-a3f2b8c1
24
23
  creating kv table ✓ zap-kv
25
24
  configuring iam ✓
@@ -31,33 +30,38 @@ npm run init
31
30
 
32
31
  ---
33
32
 
34
- ## Local dev
33
+ ## Deploy a handler
35
34
 
36
35
  ```bash
37
- npm run dev # → http://localhost:3000
36
+ npx @kirkelliott/zap deploy hello.zap
38
37
  ```
39
38
 
40
- Place `.zap` files in the project root. `GET /proxy` maps to `./proxy.zap`. Uses real DynamoDB for `kv` — same table as production.
39
+ Or install the CLI once and drop the `npx`:
40
+
41
+ ```bash
42
+ npm install -g @kirkelliott/zap
43
+ zap deploy hello.zap
44
+ ```
41
45
 
42
46
  ---
43
47
 
44
48
  ## The .zap format
45
49
 
46
- A `.zap` file exports a default async function. That function is the handler.
50
+ A `.zap` file is a JavaScript module that exports one function. That function handles the request.
47
51
 
48
52
  ```js
49
53
  export default async (req) => {
50
- return { status: 200, body: 'hello' }
54
+ return { body: `hello ${req.query.name ?? 'world'}` }
51
55
  }
52
56
  ```
53
57
 
54
58
  ### Request
55
59
 
56
60
  ```ts
57
- req.method // 'GET' | 'POST' | 'PUT' | 'DELETE' | ...
58
- req.path // '/proxy'
59
- req.query // Record<string, string> e.g. { url: 'https://...' }
60
- req.headers // Record<string, string>
61
+ req.method // 'GET' | 'POST' | ...
62
+ req.path // '/hello'
63
+ req.query // { name: 'elliott' }
64
+ req.headers // { 'content-type': '...' }
61
65
  req.body // string | null
62
66
  ```
63
67
 
@@ -67,35 +71,34 @@ req.body // string | null
67
71
  {
68
72
  status?: number // default 200
69
73
  headers?: Record<string, string>
70
- body?: string | object // objects are JSON-serialized
74
+ body?: string | object // objects become JSON automatically
71
75
  }
72
76
  ```
73
77
 
74
- ### Built-ins
78
+ ---
79
+
80
+ ## Built-ins
75
81
 
76
- Everything available globally inside every `.zap` file:
82
+ These are available in every `.zap` file — no imports needed:
77
83
 
78
- | Name | Description |
84
+ | Name | What it does |
79
85
  |---|---|
80
- | `fetch` | Standard web `fetch` |
81
- | `kv` | Persistent key/value store (DynamoDB) |
82
- | `zap(name)` | Import another `.zap` file from S3 |
86
+ | `fetch` | HTTP requests |
87
+ | `kv` | Persistent key/value storage |
88
+ | `zap(name)` | Load another `.zap` from the same bucket |
83
89
  | `crypto` | Web Crypto API |
84
- | `URL`, `URLSearchParams` | URL utilities |
85
- | `Buffer` | Node.js Buffer |
86
- | `console` | Logging (goes to CloudWatch) |
87
- | `setTimeout`, `clearTimeout` | Timers |
90
+ | `URL`, `URLSearchParams` | URL parsing |
91
+ | `Buffer` | Binary data |
92
+ | `console` | Logs to CloudWatch |
88
93
  | `process.env` | Environment variables |
89
94
 
90
95
  ---
91
96
 
92
- ## kv — persistent storage
93
-
94
- `kv` is a built-in key/value store backed by DynamoDB. No setup, no imports, no config — it's just there.
97
+ ## kv — storage that persists
95
98
 
96
99
  ```js
97
- await kv.set('key', value) // value can be string, number, object, array
98
- await kv.get('key') // returns the value, or null if not found
100
+ await kv.set('key', value) // string, number, object, array — anything
101
+ await kv.get('key') // returns the value, or null
99
102
  await kv.del('key') // delete
100
103
  ```
101
104
 
@@ -108,46 +111,45 @@ export default async (req) => {
108
111
  }
109
112
  ```
110
113
 
111
- ---
114
+ No database to provision. It's just there.
112
115
 
113
- ## zap() — imports
116
+ ---
114
117
 
115
- Any `.zap` file can import another using `zap(name)`. S3 is the module registry.
118
+ ## zap() one handler can load another
116
119
 
117
120
  ```js
118
- // utils/greet.zap
121
+ // utils/auth.zap
119
122
  export default {
120
- hello: (name) => `hello ${name}`,
123
+ verify: (token) => token === process.env.SECRET,
121
124
  }
122
125
  ```
123
126
 
124
127
  ```js
125
128
  // api.zap
126
129
  export default async (req) => {
127
- const greet = await zap('utils/greet')
128
- return { body: greet.hello(req.query.name ?? 'world') }
130
+ const auth = await zap('utils/auth')
131
+ if (!auth.verify(req.headers.authorization))
132
+ return { status: 401, body: 'unauthorized' }
133
+ return { body: 'ok' }
129
134
  }
130
135
  ```
131
136
 
132
- `zap('utils/greet')` fetches `utils/greet.zap` from the same bucket. Imports can be nested — a `.zap` file can `zap()` other `.zap` files. The bucket is the module system.
137
+ S3 is the module system. Drop a file in, import it anywhere.
133
138
 
134
139
  ---
135
140
 
136
- ## @cron — scheduled handlers
141
+ ## @cron — run on a schedule
137
142
 
138
- Add `// @cron <expr>` as the first line. When deployed, `zap deploy` automatically creates an EventBridge rule that triggers the handler on schedule.
143
+ First line `// @cron <expr>` turns any handler into a scheduled job. `zap deploy` wires up the EventBridge rule automatically.
139
144
 
140
145
  ```js
141
146
  // @cron 0 * * * *
142
147
  export default async () => {
143
148
  await kv.set('heartbeat', new Date().toISOString())
144
- console.log('tick')
145
149
  }
146
150
  ```
147
151
 
148
- Standard 5-field cron expressions (`minute hour day month weekday`). `zap rm` removes the EventBridge rule alongside the S3 file.
149
-
150
- Cron handlers receive no request argument. All built-ins (`kv`, `fetch`, `zap()`, etc.) are available.
152
+ Standard cron syntax: `minute hour day month weekday`. `zap rm` removes the schedule when you remove the file.
151
153
 
152
154
  ---
153
155
 
@@ -155,8 +157,6 @@ Cron handlers receive no request argument. All built-ins (`kv`, `fetch`, `zap()`
155
157
 
156
158
  ### CORS proxy
157
159
 
158
- Fetch any remote URL server-side, bypassing browser CORS restrictions.
159
-
160
160
  ```js
161
161
  // proxy.zap
162
162
  export default async (req) => {
@@ -179,89 +179,62 @@ export default async (req) => {
179
179
  GET https://your-endpoint/proxy?url=https://api.example.com/data
180
180
  ```
181
181
 
182
- ### Stateful counter
183
-
184
- ```js
185
- // counter.zap
186
- export default async (req) => {
187
- const count = ((await kv.get('visits')) ?? 0) + 1
188
- await kv.set('visits', count)
189
- return { body: { visits: count } }
190
- }
191
- ```
192
-
193
- ### Hourly heartbeat
194
-
195
- ```js
196
- // @cron 0 * * * *
197
- export default async () => {
198
- await kv.set('heartbeat', new Date().toISOString())
199
- }
200
- ```
201
-
202
182
  ---
203
183
 
204
184
  ## CLI
205
185
 
206
186
  ```
207
- zap init Provision AWS infra and deploy the runtime
187
+ zap init Provision AWS and deploy the runtime
208
188
  zap deploy <file|directory> Upload .zap file(s) to S3
209
- zap rm <name> Remove a handler (and its cron rule if any)
189
+ zap rm <name> Remove a handler (and its cron rule)
210
190
  zap ls List deployed handlers
191
+ zap demo Deploy the built-in demo handlers
192
+ zap repair Fix Lambda permissions if the URL stops working
211
193
  ```
212
194
 
213
- **Options**
214
-
215
- ```
216
- -r, --region <region> AWS region for init (default: us-east-1)
217
- -b, --bucket <bucket> S3 bucket name (default: reads from .zaprc)
218
- ```
219
-
220
- After `init`, a `.zaprc` file is written to the project root. All subsequent commands read the bucket name and function ARN from it automatically — no flags needed.
195
+ `init` writes a `.zaprc` to the project directory. All other commands read bucket and region from it — no flags needed.
221
196
 
222
197
  ---
223
198
 
224
- ## AWS infrastructure
199
+ ## What gets created
225
200
 
226
- Everything provisioned by `npm run init`:
201
+ `zap init` provisions six things on your AWS account:
227
202
 
228
- | Resource | Purpose |
203
+ | Resource | What it is |
229
204
  |---|---|
230
- | S3 bucket | Stores `.zap` files |
231
- | DynamoDB table (`zap-kv`) | Backs the `kv` built-in |
232
- | IAM role (`zap-runtime-role`) | Lambda execution role |
233
- | Lambda function (`zap-runtime`, Node 20) | The runtime |
234
- | Lambda Function URL | Public HTTPS endpoint — no API Gateway |
235
- | EventBridge rules | One per `@cron` handler (created on deploy) |
205
+ | S3 bucket | Where your `.zap` files live |
206
+ | DynamoDB table | Backs `kv` |
207
+ | IAM role | Lets Lambda read S3 and write DynamoDB |
208
+ | Lambda function | The runtime that runs your handlers |
209
+ | Lambda Function URL | Your public HTTPS endpoint |
210
+ | EventBridge rules | One per `@cron` handler |
236
211
 
237
212
  ---
238
213
 
239
214
  ## Cost
240
215
 
241
- Runs within AWS always-free tier limits at zero cost for typical personal or small-team usage:
216
+ All within the AWS permanent free tier:
242
217
 
243
218
  | Service | Free tier |
244
219
  |---|---|
245
- | Lambda | 1M requests/month, 400K GB-seconds — permanent |
246
- | S3 | 5GB storage, 20K GET requests/month — permanent |
247
- | DynamoDB | 25 WCU/RCU, 25GB storage — permanent |
248
- | EventBridge | 14M scheduled invocations/month — permanent |
220
+ | Lambda | 1M requests/month, 400K GB-seconds |
221
+ | S3 | 5GB, 20K GET requests/month |
222
+ | DynamoDB | 25 WCU/RCU, 25GB |
223
+ | EventBridge | 14M scheduled invocations/month |
249
224
 
250
225
  ---
251
226
 
252
227
  ## How it works
253
228
 
254
- The runtime is a single Lambda function. On each HTTP request:
255
-
256
- 1. Parse the URL path → derive the S3 key (`/proxy` → `proxy.zap`)
257
- 2. Fetch the `.zap` source from S3
258
- 3. Evaluate it in a `vm` sandbox with the built-in globals
259
- 4. Call the exported handler with the request
260
- 5. Return the response
229
+ One Lambda function runs permanently. Every request:
261
230
 
262
- For `@cron` handlers, EventBridge sends `{ zap: { cron: "handler-name" } }` as the Lambda payload. The runtime detects this and invokes the handler with no request argument.
231
+ 1. Parses the path `/proxy` `proxy.zap`
232
+ 2. Fetches that file from S3
233
+ 3. Evaluates it in a sandboxed JS environment
234
+ 4. Calls the exported function with the request
235
+ 5. Returns the response
263
236
 
264
- `zap(name)` inside a handler triggers the same fetch-and-eval cycle recursively, with the same loader — so the entire module graph lives in S3.
237
+ Updating a handler means updating a file in S3. The runtime always reads fresh.
265
238
 
266
239
  ---
267
240
 
@@ -0,0 +1,21 @@
1
+ export default async (req) => {
2
+ const count = ((await kv.get('visitors')) ?? 0) + 1
3
+ await kv.set('visitors', count)
4
+ return {
5
+ headers: { 'content-type': 'text/html' },
6
+ body: `<!doctype html>
7
+ <html>
8
+ <head><meta charset="utf-8"><title>visitors</title>
9
+ <style>
10
+ body { font-family: sans-serif; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; background: #000; color: #fff; }
11
+ h1 { font-size: 20vw; font-weight: 900; letter-spacing: -0.05em; }
12
+ p { position: fixed; bottom: 2rem; font-size: 1rem; opacity: 0.4; }
13
+ </style>
14
+ </head>
15
+ <body>
16
+ <h1>${count}</h1>
17
+ <p>visitors</p>
18
+ </body>
19
+ </html>`
20
+ }
21
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kirkelliott/zap",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "Drop a .zap file in S3. It becomes an API endpoint.",
5
5
  "main": "dist/handler.js",
6
6
  "bin": {