dropsh 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.
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { createCollection } from "./collection.js";
|
|
2
|
+
import { toResource } from "./resource.js";
|
|
3
|
+
import { resolveType } from "./types.js";
|
|
1
4
|
function joinUrl(base, prefix, path) {
|
|
2
5
|
const b = base.replace(/\/+$/, "");
|
|
3
6
|
const p = prefix.startsWith("/") ? prefix : `/${prefix}`;
|
|
@@ -22,7 +25,7 @@ export function createJsonApiClient(opts) {
|
|
|
22
25
|
return { ok: true };
|
|
23
26
|
return JSON.parse(res.body);
|
|
24
27
|
}
|
|
25
|
-
|
|
28
|
+
const client = {
|
|
26
29
|
async get(path, params) {
|
|
27
30
|
const qs = params ? `?${params.getQueryString()}` : "";
|
|
28
31
|
return send("GET", joinUrl(opts.baseUrl, opts.prefix, path) + qs);
|
|
@@ -42,5 +45,46 @@ export function createJsonApiClient(opts) {
|
|
|
42
45
|
"Content-Disposition": `file; filename="${filename}"`,
|
|
43
46
|
});
|
|
44
47
|
},
|
|
48
|
+
collection(type) {
|
|
49
|
+
return createCollection(client, type);
|
|
50
|
+
},
|
|
51
|
+
async resource(type, id) {
|
|
52
|
+
const { path } = resolveType(type);
|
|
53
|
+
const doc = (await client.get(`${path}/${id}`));
|
|
54
|
+
return toResource(doc.data);
|
|
55
|
+
},
|
|
56
|
+
async create(type, body) {
|
|
57
|
+
const { path, type: resourceType } = resolveType(type);
|
|
58
|
+
const doc = (await client.post(path, {
|
|
59
|
+
data: { type: resourceType, ...body },
|
|
60
|
+
}));
|
|
61
|
+
return toResource(doc.data);
|
|
62
|
+
},
|
|
63
|
+
async update(type, id, body) {
|
|
64
|
+
const { path, type: resourceType } = resolveType(type);
|
|
65
|
+
const doc = (await client.patch(`${path}/${id}`, {
|
|
66
|
+
data: { type: resourceType, id, ...body },
|
|
67
|
+
}));
|
|
68
|
+
return toResource(doc.data);
|
|
69
|
+
},
|
|
70
|
+
async upsert(type, match, body) {
|
|
71
|
+
const existing = await client.collection(type).where(match.path, "=", match.value).first();
|
|
72
|
+
if (existing)
|
|
73
|
+
return client.update(type, existing.id, body);
|
|
74
|
+
return client.create(type, body);
|
|
75
|
+
},
|
|
76
|
+
async me() {
|
|
77
|
+
const doc = (await client.get(""));
|
|
78
|
+
const me = doc?.meta?.links?.me;
|
|
79
|
+
if (me?.meta?.id)
|
|
80
|
+
return me.meta.id;
|
|
81
|
+
if (me?.href) {
|
|
82
|
+
const segment = me.href.split("/").filter(Boolean).pop();
|
|
83
|
+
if (segment)
|
|
84
|
+
return segment;
|
|
85
|
+
}
|
|
86
|
+
throw new Error("Unable to resolve current user uuid from root document meta.links.me");
|
|
87
|
+
},
|
|
45
88
|
};
|
|
89
|
+
return client;
|
|
46
90
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { DrupalJsonApiParams } from "drupal-jsonapi-params";
|
|
2
|
+
import { toResource } from "./resource.js";
|
|
3
|
+
import { resolveType } from "./types.js";
|
|
4
|
+
export function createCollection(client, spec, params = new DrupalJsonApiParams()) {
|
|
5
|
+
const resolved = resolveType(spec);
|
|
6
|
+
const collection = {
|
|
7
|
+
where(path, op, value) {
|
|
8
|
+
if (op === "=")
|
|
9
|
+
params.addFilter(path, String(value));
|
|
10
|
+
else
|
|
11
|
+
params.addFilter(path, String(value), op);
|
|
12
|
+
return collection;
|
|
13
|
+
},
|
|
14
|
+
whereIn(path, values) {
|
|
15
|
+
params.addFilter(path, values.map(String), "IN");
|
|
16
|
+
return collection;
|
|
17
|
+
},
|
|
18
|
+
notExists(path) {
|
|
19
|
+
params.addFilter(path, null, "IS NULL");
|
|
20
|
+
return collection;
|
|
21
|
+
},
|
|
22
|
+
fields(names) {
|
|
23
|
+
params.addFields(resolved.type, names);
|
|
24
|
+
return collection;
|
|
25
|
+
},
|
|
26
|
+
sort(path, dir) {
|
|
27
|
+
params.addSort(path, dir);
|
|
28
|
+
return collection;
|
|
29
|
+
},
|
|
30
|
+
page(limit) {
|
|
31
|
+
params.addPageLimit(limit);
|
|
32
|
+
return collection;
|
|
33
|
+
},
|
|
34
|
+
async list() {
|
|
35
|
+
const doc = (await client.get(resolved.path, params));
|
|
36
|
+
const data = doc?.data ?? [];
|
|
37
|
+
return data.map(toResource);
|
|
38
|
+
},
|
|
39
|
+
async first() {
|
|
40
|
+
const results = await collection.page(1).list();
|
|
41
|
+
return results[0] ?? null;
|
|
42
|
+
},
|
|
43
|
+
async count() {
|
|
44
|
+
const results = await collection.fields([]).list();
|
|
45
|
+
return results.length;
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
return collection;
|
|
49
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ergonomic accessors over a raw JSON:API resource object.
|
|
3
|
+
*
|
|
4
|
+
* A {@link Resource} wraps the `data` member of a JSON:API document and exposes
|
|
5
|
+
* typed helpers for attributes and relationships so callers stop digging through
|
|
6
|
+
* nested `attributes`/`relationships` structures by hand.
|
|
7
|
+
*/
|
|
8
|
+
export function toResource(obj) {
|
|
9
|
+
return {
|
|
10
|
+
id: obj.id,
|
|
11
|
+
type: obj.type,
|
|
12
|
+
raw: obj,
|
|
13
|
+
attr(key) {
|
|
14
|
+
return obj.attributes?.[key];
|
|
15
|
+
},
|
|
16
|
+
rel(key) {
|
|
17
|
+
const data = obj.relationships?.[key]?.data;
|
|
18
|
+
if (data === undefined || data === null)
|
|
19
|
+
return null;
|
|
20
|
+
if (Array.isArray(data))
|
|
21
|
+
return data[0]?.id ?? null;
|
|
22
|
+
return data.id;
|
|
23
|
+
},
|
|
24
|
+
rels(key) {
|
|
25
|
+
const data = obj.relationships?.[key]?.data;
|
|
26
|
+
if (data === undefined || data === null)
|
|
27
|
+
return [];
|
|
28
|
+
if (Array.isArray(data))
|
|
29
|
+
return data.map((d) => d.id);
|
|
30
|
+
return [data.id];
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON:API resource type/path normalization.
|
|
3
|
+
*
|
|
4
|
+
* Drupal JSON:API addresses resources by a `entity/bundle` path (e.g.
|
|
5
|
+
* `node/article`) but identifies them in document envelopes by a `entity--bundle`
|
|
6
|
+
* type (e.g. `node--article`). This helper accepts either form plus a convenient
|
|
7
|
+
* single-segment shorthand and produces both representations.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Resolve a loose type specifier into its `{ path, type }` pair.
|
|
11
|
+
*
|
|
12
|
+
* Accepted forms:
|
|
13
|
+
* - `'gaia_run'` -> `{ path: 'gaia_run/gaia_run', type: 'gaia_run--gaia_run' }`
|
|
14
|
+
* - `'entity/bundle'` -> `{ path: 'entity/bundle', type: 'entity--bundle' }`
|
|
15
|
+
* - `'entity--bundle'` -> `{ path: 'entity/bundle', type: 'entity--bundle' }`
|
|
16
|
+
*/
|
|
17
|
+
export function resolveType(spec) {
|
|
18
|
+
if (spec.includes("--")) {
|
|
19
|
+
return { type: spec, path: spec.replace(/--/g, "/") };
|
|
20
|
+
}
|
|
21
|
+
if (spec.includes("/")) {
|
|
22
|
+
return { path: spec, type: spec.replace(/\//g, "--") };
|
|
23
|
+
}
|
|
24
|
+
return { path: `${spec}/${spec}`, type: `${spec}--${spec}` };
|
|
25
|
+
}
|
package/dist/src/plugin-api.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { basicAuthPlugin, basicAuthProvider } from "./core/auth/basic.js";
|
|
2
2
|
export { loadConfig } from "./core/config.js";
|
|
3
3
|
export { createHttpClient } from "./core/http.js";
|
|
4
|
+
export { createJsonApiClient } from "./core/jsonapi/client.js";
|
|
4
5
|
export { AuthError, ConfigError, HttpError } from "./errors.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dropsh",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Entity-agnostic helper CLI for Drupal 11 JSON:API, used by the dropsh Claude skill for editorial publishing workflows.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -62,6 +62,6 @@
|
|
|
62
62
|
"lint:fix": "biome check --write src/ packages/ plugins/ tests/integrations/helpers/ tests/integrations/schema/ tests/integrations/*.ts",
|
|
63
63
|
"drupal:up": "bash tests/integrations/bin/drupal-up.sh",
|
|
64
64
|
"drupal:down": "bash tests/integrations/bin/drupal-down.sh",
|
|
65
|
-
"release": "
|
|
65
|
+
"release": "node scripts/release.mjs"
|
|
66
66
|
}
|
|
67
67
|
}
|