@littlebigbrain/create 0.3.0 → 0.4.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.
@@ -0,0 +1,150 @@
1
+ {
2
+ "q3-customers.csv": {
3
+ "settings": { "className": "Customer", "key": "Customer ID", "represents": { "class": "Report" } },
4
+ "rows": 8,
5
+ "rows_without_key": 1,
6
+ "values_left_out": 1,
7
+ "columns": [
8
+ "Customer ID → customer_id",
9
+ "Email → email",
10
+ "Address → address",
11
+ "City → city",
12
+ "Customer since → customer_since"
13
+ ],
14
+ "columns_left_out": ["Name", "Account manager"],
15
+ "represents": "Q3 customers",
16
+ "instances": [
17
+ {
18
+ "key": "C-1001",
19
+ "name": "Harbor Lane Bakery",
20
+ "row": 2,
21
+ "properties": {
22
+ "customer_id": "C-1001",
23
+ "email": "orders@harborlane.example",
24
+ "address": "12 Quay Street",
25
+ "city": "Lisbon",
26
+ "customer_since": "2021-03-14T00:00:00Z"
27
+ }
28
+ },
29
+ {
30
+ "key": "C-1002",
31
+ "name": "Okafor, Ada",
32
+ "row": 3,
33
+ "properties": {
34
+ "customer_id": "C-1002",
35
+ "email": "ada.okafor@mail.example",
36
+ "address": "4 Palm Avenue",
37
+ "city": "Lagos",
38
+ "customer_since": "2019-11-02T00:00:00Z"
39
+ }
40
+ },
41
+ {
42
+ "key": "C-1003",
43
+ "name": "Studio \"Nord\"",
44
+ "row": 4,
45
+ "properties": {
46
+ "customer_id": "C-1003",
47
+ "email": "hello@studionord.example",
48
+ "address": "Strandgata 9",
49
+ "city": "Oslo",
50
+ "customer_since": "2022-06-30T00:00:00Z"
51
+ }
52
+ },
53
+ {
54
+ "key": "C-1004",
55
+ "name": "Meridian Tutoring",
56
+ "row": 5,
57
+ "why": "The address holds a line break inside its quotes, so the row spans two lines. 'not a date' does not take the type of customer_since and is left out.",
58
+ "properties": {
59
+ "customer_id": "C-1004",
60
+ "email": "info@meridian.example",
61
+ "address": "Rua das Flores 21\nFloor 2",
62
+ "city": "Porto"
63
+ }
64
+ },
65
+ {
66
+ "key": "C-1005",
67
+ "name": "Lindqvist Maja",
68
+ "row": 6,
69
+ "properties": {
70
+ "customer_id": "C-1005",
71
+ "email": "maja@lindqvist.example",
72
+ "address": "Kungsgatan 3",
73
+ "city": "Uppsala",
74
+ "customer_since": "2020-01-20T00:00:00Z"
75
+ }
76
+ },
77
+ {
78
+ "key": "C-1006",
79
+ "name": "Calder Repair Shop",
80
+ "row": 8,
81
+ "why": "Row 7 is blank. The email cell is empty, so the instance has no email.",
82
+ "properties": {
83
+ "customer_id": "C-1006",
84
+ "address": "1 Mill Lane",
85
+ "city": "Leeds",
86
+ "customer_since": "2023-09-05T00:00:00Z"
87
+ }
88
+ }
89
+ ],
90
+ "leave_out": [{ "row": 9, "why": "The row has no Customer ID: no instance." }]
91
+ },
92
+ "q3-orders.tsv": {
93
+ "settings": { "className": "Order", "key": "Order ID", "represents": { "class": "Report" } },
94
+ "rows": 4,
95
+ "rows_without_key": 0,
96
+ "values_left_out": 1,
97
+ "represents": "Q3 orders",
98
+ "instances": [
99
+ {
100
+ "key": "O-5001",
101
+ "name": "O-5001",
102
+ "row": 2,
103
+ "properties": {
104
+ "order_id": "O-5001",
105
+ "customer_id": "C-1001",
106
+ "placed_on": "2026-07-03T00:00:00Z",
107
+ "total": 182.4,
108
+ "status": "paid"
109
+ }
110
+ },
111
+ {
112
+ "key": "O-5002",
113
+ "name": "O-5002",
114
+ "row": 3,
115
+ "why": "1250,50 has a decimal comma; the status is quoted.",
116
+ "properties": {
117
+ "order_id": "O-5002",
118
+ "customer_id": "C-1002",
119
+ "placed_on": "2026-07-19T00:00:00Z",
120
+ "total": 1250.5,
121
+ "status": "on hold"
122
+ }
123
+ },
124
+ {
125
+ "key": "O-5003",
126
+ "name": "O-5003",
127
+ "row": 4,
128
+ "properties": {
129
+ "order_id": "O-5003",
130
+ "customer_id": "C-1003",
131
+ "placed_on": "2026-08-02T00:00:00Z",
132
+ "total": 64,
133
+ "status": "paid"
134
+ }
135
+ },
136
+ {
137
+ "key": "O-5004",
138
+ "name": "O-5004",
139
+ "row": 5,
140
+ "why": "n/a does not take the type of total and is left out.",
141
+ "properties": {
142
+ "order_id": "O-5004",
143
+ "customer_id": "C-1001",
144
+ "placed_on": "2026-09-28T00:00:00Z",
145
+ "status": "cancelled"
146
+ }
147
+ }
148
+ ]
149
+ }
150
+ }
@@ -0,0 +1,11 @@
1
+ [
2
+ { "name": "customer_id", "value_type": "keyword" },
3
+ { "name": "email", "value_type": "keyword" },
4
+ { "name": "address", "value_type": "text" },
5
+ { "name": "city", "value_type": "keyword" },
6
+ { "name": "customer_since", "value_type": "date_time" },
7
+ { "name": "order_id", "value_type": "keyword" },
8
+ { "name": "placed_on", "value_type": "date_time" },
9
+ { "name": "total", "value_type": "f64" },
10
+ { "name": "status", "value_type": "keyword" }
11
+ ]
@@ -0,0 +1,10 @@
1
+ Customer ID,Name,Email,Address,City,Customer since,Account manager
2
+ C-1001,Harbor Lane Bakery,orders@harborlane.example,12 Quay Street,Lisbon,2021-03-14,R. Silva
3
+ C-1002,"Okafor, Ada",ada.okafor@mail.example,4 Palm Avenue,Lagos,2019-11-02,T. Bello
4
+ C-1003,"Studio ""Nord""",hello@studionord.example,Strandgata 9,Oslo,2022-06-30,K. Berg
5
+ C-1004,Meridian Tutoring,info@meridian.example,"Rua das Flores 21
6
+ Floor 2",Porto,not a date,R. Silva
7
+ C-1005,Lindqvist Maja,maja@lindqvist.example,Kungsgatan 3,Uppsala,2020-01-20,K. Berg
8
+
9
+ C-1006,Calder Repair Shop,,1 Mill Lane,Leeds,2023-09-05,T. Bello
10
+ ,Walk-in customer,walkin@shop.example,,Leeds,2024-02-11,
@@ -0,0 +1,5 @@
1
+ Order ID Customer ID Placed on Total Status
2
+ O-5001 C-1001 2026-07-03 182.40 paid
3
+ O-5002 C-1002 2026-07-19 1250,50 "on hold"
4
+ O-5003 C-1003 2026-08-02 64 paid
5
+ O-5004 C-1001 2026-09-28 n/a cancelled
@@ -0,0 +1,5 @@
1
+ [
2
+ { "name": "Q3 customers", "text": "The customer list at the end of the third quarter." },
3
+ { "name": "Q3 orders", "text": "The orders of the third quarter." },
4
+ { "name": "Q2 customers", "text": "The customer list at the end of the second quarter." }
5
+ ]
@@ -0,0 +1,56 @@
1
+ // The samples of rows, for `npm run try`: an invented customer list
2
+ // (samples/q3-customers.csv) and order table (samples/q3-orders.tsv), the
3
+ // properties their columns fill (samples/properties.json), and three
4
+ // reports (samples/reports.json) that the files' names can name.
5
+ import { readFile } from "node:fs/promises";
6
+ import { join } from "node:path";
7
+ import type { LbbClient, Schemas } from "@littlebigbrain/client";
8
+ import { defaults } from "./workflow.js";
9
+
10
+ /** The sample files and their media types. */
11
+ export const SAMPLE_FILES = [
12
+ { name: "q3-customers.csv", contentType: "text/csv" },
13
+ { name: "q3-orders.tsv", contentType: "text/tab-separated-values" },
14
+ ];
15
+
16
+ /** Add the properties and the reports, then upload the sample files to the client's graph. */
17
+ export async function loadSamples(client: LbbClient, dir: string): Promise<void> {
18
+ const properties = JSON.parse(await readFile(join(dir, "properties.json"), "utf8")) as {
19
+ name: string;
20
+ value_type: string;
21
+ }[];
22
+ const reports = JSON.parse(await readFile(join(dir, "reports.json"), "utf8")) as {
23
+ name: string;
24
+ text: string;
25
+ }[];
26
+ const reportClass = defaults.represents?.class ?? "Report";
27
+ const ops: Schemas["OntologyEvolveOp"][] = [
28
+ { op: "add_entity_type", name: reportClass },
29
+ { op: "add_property", name: "text", value_type: "text" },
30
+ ...properties.map((p) => ({
31
+ op: "add_property" as const,
32
+ name: p.name,
33
+ value_type: p.value_type,
34
+ })),
35
+ ];
36
+ await client.ontology.evolve({ ops });
37
+ await client.commit(
38
+ {
39
+ entity_properties: reports.map((report) => ({
40
+ type: reportClass,
41
+ name: report.name,
42
+ properties: { text: report.text },
43
+ })),
44
+ },
45
+ { idempotencyKey: "rows-samples" },
46
+ );
47
+ console.log(`Committed ${reports.length} ${reportClass} records`);
48
+ for (const file of SAMPLE_FILES) {
49
+ const bytes = await readFile(join(dir, file.name));
50
+ const uploaded = await client.files.upload(new Uint8Array(bytes), {
51
+ name: file.name,
52
+ contentType: file.contentType,
53
+ });
54
+ console.log(`Uploaded ${file.name} (${uploaded.file.sha256.slice(0, 12)})`);
55
+ }
56
+ }
@@ -0,0 +1,18 @@
1
+ // Create the workflow on your graph: add the class and the relation it
2
+ // writes, then create the named workflow whose content trigger sends it each
3
+ // CSV and TSV upload. Each call is safe to repeat.
4
+ //
5
+ // LBB_URL=... LBB_API_KEY=... LBB_GRAPH=... npm run setup
6
+ import { LbbClient, lbbActivities } from "@littlebigbrain/client";
7
+ import { ontology, trigger } from "./workflow.js";
8
+
9
+ const graph = process.env.LBB_GRAPH ?? "main";
10
+ const client = new LbbClient({
11
+ baseUrl: process.env.LBB_URL ?? "http://127.0.0.1:7400",
12
+ apiKey: process.env.LBB_API_KEY,
13
+ graph,
14
+ });
15
+ await lbbActivities(client).evolve(ontology());
16
+ const created = await client.workflows.named.put(trigger());
17
+ console.log(`Created ${created.name} on the graph ${graph}.`);
18
+ console.log(`Serve it with LBB_GRAPH=${graph} npm start.`);
@@ -0,0 +1,32 @@
1
+ // Try the workflow on its samples: create a graph (default try-rows), add
2
+ // the sample properties and reports, upload the sample files, and create the
3
+ // workflow there. Then serve it with npm start and the same LBB_GRAPH: the
4
+ // content trigger reads the files uploaded before it too.
5
+ //
6
+ // LBB_URL=... LBB_API_KEY=... LBB_GRAPH=try-rows npm run try
7
+ import { fileURLToPath } from "node:url";
8
+ import { LbbClient, LbbError, lbbActivities } from "@littlebigbrain/client";
9
+ import { loadSamples } from "./samples.js";
10
+ import { ontology, trigger } from "./workflow.js";
11
+
12
+ const graph = process.env.LBB_GRAPH ?? "try-rows";
13
+ const client = new LbbClient({
14
+ baseUrl: process.env.LBB_URL ?? "http://127.0.0.1:7400",
15
+ apiKey: process.env.LBB_API_KEY,
16
+ graph,
17
+ });
18
+ try {
19
+ await client.graph(graph).create();
20
+ console.log(`Created the graph ${graph}`);
21
+ } catch (error) {
22
+ // 409: the graph exists already.
23
+ if (!(error instanceof LbbError && error.status === 409)) throw error;
24
+ }
25
+ await lbbActivities(client).evolve(ontology());
26
+ await loadSamples(client, fileURLToPath(new URL("../samples/", import.meta.url)));
27
+ const created = await client.workflows.named.put(trigger());
28
+ console.log(`Created ${created.name} on the graph ${graph}.`);
29
+ console.log(`Serve it with LBB_GRAPH=${graph} npm start, and follow it with:`);
30
+ console.log(
31
+ ` curl "$LBB_URL/v1/workflows/named/queue?graph=${graph}&name=${created.name}" -H "Authorization: Bearer $LBB_API_KEY"`,
32
+ );
@@ -0,0 +1,24 @@
1
+ // Serve the workflow: the worker claims the turns of its content trigger's
2
+ // messages and runs them, one at a time. Run more workers for more at once.
3
+ //
4
+ // LBB_URL=... LBB_API_KEY=... LBB_GRAPH=... npm start
5
+ import { LbbClient, WorkflowWorker, lbbActivities } from "@littlebigbrain/client";
6
+ import { createWorkflow } from "./workflow.js";
7
+
8
+ const graph = process.env.LBB_GRAPH ?? "main";
9
+ const client = new LbbClient({
10
+ baseUrl: process.env.LBB_URL ?? "http://127.0.0.1:7400",
11
+ apiKey: process.env.LBB_API_KEY,
12
+ graph,
13
+ });
14
+ // The client reads the files' bytes and the derivations.
15
+ const workflow = createWorkflow(lbbActivities(client), client);
16
+ const stop = new AbortController();
17
+ process.once("SIGINT", () => stop.abort());
18
+ process.once("SIGTERM", () => stop.abort());
19
+ console.log(
20
+ `Serving ${workflow.name}@${workflow.version} for the graph ${graph}. Stop with Ctrl-C.`,
21
+ );
22
+ await new WorkflowWorker(client, [workflow], {
23
+ worker: process.env.LBB_WORKER_ID ?? `${workflow.name}-1`,
24
+ }).run({ signal: stop.signal });