@foxtware/mineral 0.1.39 → 1.0.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/AGENTS.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  Notes for AI assistants working in this directory. **Read this file at the start of mineral tasks** and follow anything here that isn't overridden by the user in chat.
4
4
 
5
+ ## Related docs
6
+
7
+ - Utils landmarks (`Processor`, `canFinish`, `Getter`, …): [`api/UTILS.md`](api/UTILS.md)
8
+ - Function layout templates: `api/_example.js`, `api/[platform]/_example*.js`
9
+
5
10
  ## Misc
6
11
  - When working in shopify, refer to gids (gid://shopify/Page/12345678) explicitly as "gids", and use "id" to refer to the number itself (12345678). "id" may still be required for use of the GraphQL API but semantically we should make this distinction.
7
12
  - funcApiConfig should not list `options` in `argNames` — it is optional. When present on the request body, `funcApi` appends it after the named args.
package/README.md CHANGED
@@ -1,21 +1,24 @@
1
1
  # mineral
2
2
 
3
- The [bedrock](https://github.com/GorgonFreeman/bedrock) middleware, refactored for compatibility with other projects and more consistent standards.
3
+ An everything middleware, centred around easy-to-bring credentials and instantly useful curl commands. Now importable into your project.
4
4
 
5
- ## Problems with bedrock
5
+ ## How to get mineralling
6
6
 
7
- - **Inconsistent response format**
8
- - `success: true` vs `success: false` for calls that succeed but return no data
9
- - No distinction between technical failures and valid business errors
10
- - Mixed use of `error` vs `errors` in responses
11
- - **Portability** `.creds.yml` and related structures resist reuse outside the repo
12
- - **Auth** — helpers can fail without retrying, especially when using creds from Upstash
13
- - **Architecture** boundaries between core and private functions could be firmer
7
+ 1. From your project, install mineral: `npm i @foxtware/mineral`.
8
+ 2. Set up a `.creds.yml` in your project directory, based on mineral's `.creds.yml.sample`. Add the credentials for the platform you want to use.
9
+ 3. Add the `dev` command to your project's `package.json`:
10
+ ```
11
+ "dev": "node --watch --watch-path=./api --watch-path=./.creds.yml --watch-path=./.env --watch-path=./hosting node_modules/@foxtware/mineral/server.js --workspace . --api_dirs api"
12
+ ```
13
+ 4. Run `npm run dev` to start a server.
14
+ 5. Use an example curl command from the file whose function you want to use, with the creds path you set up.
14
15
 
15
- ## The plan
16
+ ## Key ideas
17
+ ### The response format to rule them all
16
18
 
17
- - **The response format to rule them all**
18
- ```typescript
19
+ All functions should return data in this format.
20
+
21
+ ```typescript
19
22
  interface Response {
20
23
  ok: boolean
21
24
  data?: unknown
@@ -32,28 +35,44 @@ The [bedrock](https://github.com/GorgonFreeman/bedrock) middleware, refactored f
32
35
  }
33
36
  results?: Response[]
34
37
  }
35
- ```
36
- - `ok` — `true` if the intent executed; `false` otherwise
37
- - `data` — any info returned
38
- - `error` — when present, multiple errors go in `error.details`
39
- - `meta` — pagination, total items, etc.
40
- - `results` — array of responses from a queue or batch of calls
41
- - [Examples](_docs/standard_response_examples.md)
42
- - **BYO Creds**
43
- ```
44
- {
45
- credsPath,
46
- shopifyCredsPath,
47
- credsObject,
48
- credsProvider, // function that returns a credsObject
49
- }
50
- ```
51
- - **Monorepo structure**
52
- Mineral gets pushed to from a larger repo that can also contain private functions. Mineral should be strictly useful stuff for the public, and can be used standalone, but needs to be instantiated for serving, setting stuff like which creds file to use. This allows it to be used as part of another repo in the same HTTP/curl way as by itself. Pass `--workspace` to locate `.creds.yml` and `--api_dirs` to serve additional function directories.
38
+ ```
39
+ - `ok` — `true` if the intent executed; `false` otherwise
40
+ - `data` — any info returned
41
+ - `error` — when present, multiple errors go in `error.details`
42
+ - `meta` — pagination, total items, etc.
43
+ - `results` — array of responses from a queue or batch of calls
44
+ - [Examples](_docs/standard_response_examples.md)
45
+
46
+ ### BYO creds
47
+
48
+ Each function accepts an object like this, supplying auth info for each platform it contacts:
49
+
50
+ ```
51
+ {
52
+ credsPath,
53
+ credsObject,
54
+ credsProvider, // function that returns a credsObject
55
+ }
56
+ ```
57
+
58
+ You can set up a `.creds.yml` in your workspace directory and refer to it using `credsPath` > `platform.account`, or supply credentials inline using `credsObject`.
59
+
60
+ ### Monorepo context
61
+
62
+ Mineral gets pushed to from a larger repo. Private functions are contained in a sibling project, which imports mineral to power all of the core functionalities, and layers certain business logic on top - e.g. known creds paths.
63
+
64
+ Mineral is strictly core functionalities, useful for anyone.
65
+
66
+ Pass `--workspace` to locate `.creds.yml` and `--api_dirs` to serve additional function directories.
67
+
68
+ ### Hosting
69
+
70
+ For cloud deploy, workspaces use `hosting/.hosting.yml` and `npm run host` (same `--workspace` / `--api_dirs` flags as dev/serve). See `hosting/.hosting.yml.sample`.
71
+
72
+ In the hosting YML, you can use wrappers which are additional layers to your hosted function. For instance, you may want to require an API key in requests to your middleware while it's live, or, require a hash in the headers that matches a hash of the body.
73
+
74
+ The decoupled nature of these desires to what the function actually does and their broad application is why they're implemented in the hosting config itself, rather than in a custom instance of the function.
53
75
 
54
- For cloud deploy, workspaces use `hosting/.hosting.yml` and `npm run host` (same `--workspace` / `--api_dirs` flags as dev/serve). See `hosting/.hosting.yml.sample`.
76
+ ### AI use
55
77
 
56
- ## What the thang do
57
- - Server makes functions available from the api/ route, where an export matches the filename. Run `npm run serve`, and they're all curlable.
58
- - .creds.yml is copied into .env when deploying, so creds can be accessed while hosted. Locally, it reads from the file directly.
59
- - Cloud deploy reads `hosting/.hosting.yml` for per-function config — `before_wrappers` / `after_wrappers` like `requireHostedApiKey`, `max_instances`, schedules — and deploys each function to Google Cloud.
78
+ Not gonna lie, I have absolutely composed some of these platforms with AI. Some of them may not work, some may not stick closely to the core principles, some may use awkward auth methods - some error payloads are definitely not optimised. However, know that I have hand-coded precursors to this repo, that do largely the same thing, and that any additions benefit (or suffer) from the structure that is laid out in the earlier platforms.
package/api/UTILS.md ADDED
@@ -0,0 +1,52 @@
1
+ # Mineral utils landmarks
2
+
3
+ Cheat sheet for [`utils.js`](utils.js). Prefer this over cold-grepping the file. Exports are listed near the bottom of `utils.js` (`module.exports`).
4
+
5
+ ## Validation and args
6
+
7
+ | Export | Use when |
8
+ |--------|----------|
9
+ | `ArgsWarden` | Validate required function args. Do **not** list `options` in `argNames` — `funcApi` appends body `options` after named args. |
10
+ | `credsFromPayload` | Resolve `{ credsPath }` / `{ credsObject }` / `{ credsProvider }` into creds. |
11
+ | `valueProvided` | Default ArgsWarden singleton validator (“anything truthy/present”). |
12
+ | `objHasAny` / `objHasAll` | Object-shape validators (e.g. product identifier has `productId` or `handle`). |
13
+
14
+ ## Queues and batching
15
+
16
+ | Export | Use when |
17
+ |--------|----------|
18
+ | `Processor` | Drain a mutable pile with concurrency. Constructor options include `canFinish` (default `true`), `maxInFlightRequests`, `logFlavourText`, `onDone`. Set `this.canFinish = false` (or pass `canFinish: false`) to keep waiting when the pile is empty until you flip it true — useful when producers still fill the pile. |
19
+ | `oneTrickProcessor` | Fire-and-forget: pile of arg arrays → `func(...args)` via a `Processor`. |
20
+ | `actionSingleOrMultiple` | Single resource action that also accepts an array (or cartesian product of arrays) via `OperationQueue`. |
21
+ | `Operation` / `OperationQueue` | Lower-level queue primitives behind `actionSingleOrMultiple`. |
22
+ | `Getter` | Paginated list fetch: `paginator` + `digester`; export both `platformThingGet` and `platformThingGetter` with `.bind`-style wrappers. Call `getter.end()` to stop paging early. `run({ verbose })` defaults to quiet when `HOSTED`. |
23
+ | `FakeGetter` | Wrap a one-shot fetch (e.g. Peoplevox report) so it emits `items` / `done` like a `Getter`. |
24
+ | `ThresholdActioner` | Call an action once N `increment()` calls have happened (e.g. unlock a tagger after processors finish). |
25
+ | `MultiDex` | Index items by multiple primary keys and merge partial records (useful for cross-store joins). |
26
+
27
+ ## Shopify ids
28
+
29
+ | Export | Use when |
30
+ |--------|----------|
31
+ | `gidToId` | Strip `gid://shopify/.../123` → `"123"`. Prefer numeric **id** inputs on mineral functions; use **gid** only when the GraphQL API requires it. |
32
+
33
+ ## HTTP client
34
+
35
+ | Export | Use when |
36
+ |--------|----------|
37
+ | `FetchClient` | Platform HTTP client base (pipeline steps, base URL, auth). |
38
+ | `customFetch` | Shared fetch used inside clients; already sets `Content-Type: application/json` when there is a body — don’t set it again. |
39
+ | `fetchClient` option | Many handlers accept `fetchClient` in `options` so geode (or tests) can inject a wrapped client. |
40
+
41
+ ## Diff / inspect (sweeps)
42
+
43
+ | Export | Use when |
44
+ |--------|----------|
45
+ | `surveyObject` | Summarise / compare object fields for assess steps. |
46
+ | `diffObjects` | Field-level diffs between source and target. |
47
+ | `logDeep` | Deep console dump (local inspect). |
48
+ | `askQuestion` | Interactive confirm when `!HOSTED`. |
49
+
50
+ ## Small helpers often needed
51
+
52
+ `ensureArray`, `arrayToChunks`, `groupObjectsByFields`, `arrayPartition`, `wait`, `timeMs`, `responseArrayToResponse`, `responseResultsByOutcome`, `normalise`, `objectDigNodeAtPath`.
@@ -6,32 +6,41 @@ const { ArgsWarden } = require('../utils');
6
6
  const { shopifyProductGet, productIdentifierValidator } = require('../shopify/shopifyProductGet');
7
7
  const { shopifyProductUpdate } = require('../shopify/shopifyProductUpdate');
8
8
 
9
+ const productPayloadValidator = (productPayload) => {
10
+ const { productIdentifier, productTitle } = productPayload;
11
+ return productIdentifierValidator(productIdentifier);
12
+ };
13
+
9
14
  const argsWarden = new ArgsWarden([
10
15
  ['credsPayload', credsValidator],
11
- ['productIdentifier', productIdentifierValidator],
16
+ ['productPayload', productPayloadValidator],
12
17
  ]);
13
18
 
14
19
  const shopifyProductUpdateTrigger = async (
15
20
  credsPayload,
16
- productIdentifier,
21
+ productPayload,
17
22
  {
18
23
  apiVersion,
19
- productTitle,
20
24
  } = {},
21
25
  ) => {
22
26
 
23
27
  const rejectResponse = await argsWarden.responseIfRejectingArgs({
24
28
  credsPayload,
25
- productIdentifier,
29
+ productPayload,
26
30
  });
27
31
  if (rejectResponse) {
28
32
  return rejectResponse;
29
33
  }
30
34
 
35
+ const {
36
+ productIdentifier,
37
+ productTitle,
38
+ } = productPayload;
39
+
31
40
  if (!productTitle) {
32
41
  const productResponse = await shopifyProductGet(
33
42
  credsPayload,
34
- productIdentifier,
43
+ productPayload,
35
44
  {
36
45
  attrs: 'title',
37
46
  },
@@ -83,14 +92,13 @@ module.exports = {
83
92
  -H "Content-Type: application/json" \
84
93
  -d '{
85
94
  "credsPayload": { "credsPath": "shopify.au" },
86
- "productIdentifier": { "productId": "1234567890" }
95
+ "productPayload": { "productIdentifier": { "productId": "1234567890" } }
87
96
  }'
88
97
 
89
98
  curl -X POST "http://localhost:8000/shopifyProductUpdateTrigger" \
90
99
  -H "Content-Type: application/json" \
91
100
  -d '{
92
101
  "credsPayload": { "credsPath": "shopify.au" },
93
- "productIdentifier": { "productId": "1234567890" },
94
- "options": { "productTitle": "Ultra Strength Freeze Ray" }
102
+ "productPayload": { "productIdentifier": { "productId": "1234567890" }, "productTitle": "Ultra Strength Freeze Ray" },
95
103
  }'
96
104
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foxtware/mineral",
3
- "version": "0.1.39",
3
+ "version": "1.0.0",
4
4
  "bin": {
5
5
  "mineral": "bin/mineral.js"
6
6
  },