@blotout/qinto-cli 0.1.13 → 0.1.15

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
@@ -234,10 +234,13 @@ Three surfaces, one per side of the product:
234
234
  declares. It renders the same form the Qinto platform
235
235
  does — text, toggle, and write-only secrets — and an install stays `inactive`, receiving nothing, until every `required`
236
236
  variable is filled in. Adding an install is offered only for an app whose manifest sets `install.multiple`, and the last
237
- install is never removable — uninstall the app instead.
237
+ install is never removable — uninstall the app instead. **Analytics → ID graph** opens the visitor's anon, alias and person
238
+ objects exactly as the platform's page does: paste an anon id, an email or a person id and read its keys and tables as your
239
+ app writes into them.
238
240
  - **The demo site** is the customer's page. It loads `q.js` from the local site worker and drives every method the tag exposes —
239
241
  `track`, `identify`, `consent`, `alias`, `getSession` — then shows which app bundles `q.js` carried and which installs
240
- activated. Its **Events** panel has a button for each of the eleven standard events in
242
+ activated. Its **Visitor** panel shows the browser's anon id, the one to paste into the ID graph page, and the person id
243
+ once `identify` links one. Its **Events** panel has a button for each of the eleven standard events in
241
244
  [the standard events reference](https://docs.qinto.io): selecting one loads its documented payload into an editable box,
242
245
  and **Track it** sends whatever is in it, so you can fire the real shape or your own. Every event reaches your hooks;
243
246
  only one carrying a numeric `value` and a non-empty `orderId` — `Purchase`, among the standard ones — is also recorded as a
@@ -265,6 +268,7 @@ are namespaced by its slug, so two apps declaring a `d1` called `ledger` get a d
265
268
  | `--reset` | Discard this directory's local installs, variables, secrets and stored data first. |
266
269
  | `--tunnel` | Expose the site worker on a fresh `trycloudflare.com` hostname, so a vendor webhook can reach its endpoints. |
267
270
  | `--tunnel-name <name>` | Expose it through this named Cloudflare Tunnel instead, for a hostname that survives a restart. |
271
+ | `--tunnel-hours <hours>` | Close the tunnel after this many hours, instead of leaving it open until `app dev` stops. |
268
272
  | `--no-input` | Disable all interactive prompts, mounting every app found. |
269
273
 
270
274
  All four listeners bind `127.0.0.1` only.
@@ -297,26 +301,33 @@ register with the vendor and keeps the loopback one beside it for `curl`.
297
301
 
298
302
  `cloudflared` is downloaded and cached on first use, unless you already have one on `PATH` or at `CLOUDFLARED_PATH`.
299
303
 
300
- **The tunnel closes after an hour.** It is a public door onto your machine, and an `app dev` left running overnight would hold it
301
- open until you noticed. `dev` says how long is left every ten minutes; press `a` then Enter to add an hour, up to three hours from
302
- now. When it closes, the public URL stops being advertised and the loopback one carries on.
304
+ **The tunnel closes when `app dev` does.** It is a public door onto your machine, so quitting `dev` is what shuts it: there is no
305
+ separate clock, and a session left running overnight holds the door open until you stop it. Cloudflare places no limit of its own,
306
+ so a named tunnel will happily run for days.
307
+
308
+ **`--tunnel-hours <hours>` closes it earlier.** Give it a deadline and `dev` says how long is left every ten minutes; press `a`
309
+ then Enter to add an hour, as often as you like. When it closes, the public URL stops being advertised and the loopback one
310
+ carries on. Fractions work, so `--tunnel-hours 0.5` is half an hour. Nothing caps the number.
311
+
312
+ For a run measured in days, use `--tunnel-name`. Cloudflare gives `trycloudflare.com` no uptime guarantee and caps it at 200
313
+ in-flight requests, so a quick tunnel is a debug aid rather than something to leave up over a weekend.
303
314
 
304
315
  **A hostname that survives a restart — `--tunnel-name <name>`.** A quick tunnel is free and needs no account, but its hostname is
305
316
  new on every run, so a vendor has to be told the new one each time. A named tunnel keeps one hostname. `dev` reads it from **your
306
317
  own Cloudflare account**, never from anything Qinto holds.
307
318
 
308
- In the Cloudflare dashboard, create a tunnel and give it a **public hostname pointing at `http://127.0.0.1:4310`** — the port
309
- `--worker-port` sets. Then run `qinto app dev --tunnel-name <name>`. The first time, `dev` asks for the two things it needs and
310
- tells you how to make them:
319
+ Run `qinto app dev --tunnel-name <name>`. The first time, `dev` asks for the two things it needs and tells you how to make them:
311
320
 
312
321
  ```
313
322
  A named tunnel is read from your own Cloudflare account, and qinto has no credentials for it yet.
314
323
 
315
324
  1. Open https://dash.cloudflare.com/profile/api-tokens
316
325
  2. Select Create Token, then Create Custom Token.
317
- 3. Under Permissions, add one row: Account > Cloudflare Tunnel > Edit
318
- That is the only permission qinto needs. Do not add more.
319
- 4. Under Account Resources, select the account that holds the tunnel.
326
+ 3. Under Permissions, add: Account > Cloudflare Tunnel > Edit
327
+ To let qinto create the tunnel for you, also add: Zone > Zone > Read and Zone > DNS > Edit
328
+ Nothing more: the token is saved on this machine.
329
+ (An account-owned token lists the first row as Cloudflare One Connector: cloudflared, or Argo Tunnel (Legacy).)
330
+ 4. Under Account Resources, select the account that holds the tunnel; under Zone Resources, the zone its hostname is on.
320
331
  5. Create the token and copy it — Cloudflare shows it once.
321
332
 
322
333
  Your account ID is the last part of the dashboard URL:
@@ -330,6 +341,28 @@ saved pair and overwrite nothing — that is how CI, or a second account, runs.
330
341
  rather than paired with a credential from somewhere else. `--no-input`, and a run with no terminal, say what to set instead of
331
342
  prompting.
332
343
 
344
+ **A tunnel that does not exist yet is created for you.** When the account has no tunnel under that name, `dev` says so and asks
345
+ whether to create it. Say yes and it asks for the public hostname a vendor should call — a zone on that account or a name one
346
+ level below it, such as `hooks.example.com`; Universal SSL covers no deeper name, so a deeper one is refused before anything is
347
+ made — then makes the tunnel under the name you gave `--tunnel-name`, routes that hostname to
348
+ `http://127.0.0.1:4310` (the port `--worker-port` sets), and adds the proxied CNAME record for it to your zone. The zone is found
349
+ from the hostname, so nothing is created when no zone on the account holds it; a failure after the tunnel exists — a DNS record
350
+ already on that name, or a token without the zone rows — removes the tunnel again, so a fixed token or a different hostname is a
351
+ clean retry. `dev` never deletes a DNS record: when the record's write got no readable answer, it names the record to check in the
352
+ dashboard instead. From then on the tunnel is found by name and nothing is asked. Say no and `dev` prints the dashboard steps
353
+ instead:
354
+
355
+ ```
356
+ To create the tunnel "hooks" yourself:
357
+ 1. Open https://one.dash.cloudflare.com/<account-id>/networks/tunnels and select Create a tunnel.
358
+ 2. Choose Cloudflared and name it "hooks". Skip installing a connector — qinto runs cloudflared for you.
359
+ 3. Under Public Hostnames, add the hostname a vendor should call, with service http://127.0.0.1:4310 (the port --worker-port sets).
360
+ Then run the command again.
361
+ ```
362
+
363
+ `--no-input`, and a run with no terminal, print the same steps rather than creating anything: making a tunnel and a DNS record
364
+ in your account is a question, and a run that promised no prompts cannot ask it.
365
+
333
366
  `dev` looks the tunnel up by name and runs it on the token it fetched. cloudflared reads ingress rules top to bottom and stops at
334
367
  the first match, so the rule that decides a hostname is often not the rule naming it — an earlier catch-all, wildcard, or
335
368
  same-hostname rule takes the request first. A hostname is advertised only when **every rule that can take it points at your worker
@@ -358,7 +391,7 @@ browser, `curl` or the vendor will read. By the time the URL is printed it works
358
391
  **Only the site worker is exposed** — the same worker, and the same code, that faces the internet on a deployed site. The console
359
392
  is never tunneled: it installs apps and shows your variables, and it answers a loopback `Host` and nothing else.
360
393
 
361
- Two things behave differently from a deployed site, and `dev` says so rather than letting you discover them:
394
+ Four things behave differently from a deployed site, and `dev` says so rather than letting you discover them:
362
395
 
363
396
  - **`ai`, `vectorize`, and a `hyperdrive` with no local database URL are not bound.** Each needs a Cloudflare account or a
364
397
  database only you can name, so the app boots without those bindings and the start-up banner lists them. Give a hyperdrive its
@@ -366,6 +399,15 @@ Two things behave differently from a deployed site, and `dev` says so rather tha
366
399
  - **Two mounted apps cannot bind the same hyperdrive name to different databases.** workerd names a hyperdrive connection after
367
400
  the binding — your app's own `env.DATABASE` — and keeps the first of a name, so the second app's binding is reported inert
368
401
  instead of silently reaching the first app's database. Rename the binding, or point both at one database.
402
+ - **A `pipeline` writes to a local catalog, not to Cloudflare.** `send()` checks each record against the manifest schema, runs
403
+ the sink `select` on SQLite and appends the rows to `namespace.table` under `~/.qinto/dev/<hash>/catalog/`; `query.catalog`
404
+ reads them back with SQLite plus `to_timestamp_millis`, `date_trunc` and the `json_get_*` functions. The banner prints the
405
+ directory so `sqlite3` can open it, and `--reset` clears it.
406
+ - **An `analytics_engine` dataset is a local SQLite file.** `writeDataPoint` stores the point with the columns Analytics Engine
407
+ gives it, and `query.analytics` reads it back through the same statement gate the platform applies, plus the documented
408
+ Analytics Engine functions — a name outside that list is refused locally rather than answered by SQLite, so a query that runs
409
+ here runs deployed. The one thing it cannot reproduce is the throw: a point that breaks a documented limit is reported on the
410
+ terminal and dropped, where a deployed hook would throw. The banner prints the file, and `--reset` clears it.
369
411
 
370
412
  **The UI panel.** When your manifest declares a `ui` block, `dev` runs its build with the same base path a published panel is
371
413
  served under — `/<appId>/<version>/` — and serves the result on its own port (`--ui-port`, default 4302); every mounted app's