nothing-browser 0.0.18 → 0.0.20
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/LICENSE +20 -20
- package/README.md +253 -253
- package/dist/piggy/register/index.d.ts.map +1 -1
- package/dist/piggy.js +14 -3
- package/dist/register/index.js +14 -3
- package/nothing_browser_pig_pink.svg +58 -58
- package/package.json +4 -3
- package/piggy/cache/memory.d.ts +6 -6
- package/piggy/cache/memory.ts +37 -37
- package/piggy/client/index.d.ts +78 -78
- package/piggy/client/index.ts +567 -567
- package/piggy/human/index.d.ts +6 -6
- package/piggy/human/index.ts +52 -52
- package/piggy/intercept/scripts.d.ts +12 -12
- package/piggy/intercept/scripts.ts +152 -152
- package/piggy/launch/detect.d.ts +2 -2
- package/piggy/launch/detect.ts +42 -42
- package/piggy/launch/spawn.d.ts +5 -5
- package/piggy/launch/spawn.ts +164 -164
- package/piggy/logger/index.d.ts +2 -2
- package/piggy/logger/index.ts +58 -58
- package/piggy/open/index.d.ts +3 -3
- package/piggy/open/index.ts +4 -4
- package/piggy/pool/index.d.ts +11 -11
- package/piggy/pool/index.ts +74 -74
- package/piggy/register/index.d.ts +6 -6
- package/piggy/register/index.ts +517 -506
- package/piggy/server/index.d.ts +57 -57
- package/piggy/server/index.ts +189 -189
- package/piggy/store/index.d.ts +25 -25
- package/piggy/store/index.ts +229 -229
- package/piggy.ts +216 -216
package/piggy/store/index.ts
CHANGED
|
@@ -1,230 +1,230 @@
|
|
|
1
|
-
// piggy/store/index.ts
|
|
2
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
3
|
-
import { dirname, resolve } from "path";
|
|
4
|
-
import logger from "../logger";
|
|
5
|
-
|
|
6
|
-
// ── Types ─────────────────────────────────────────────────────────────────────
|
|
7
|
-
|
|
8
|
-
export type FieldType = "string" | "number" | "boolean" | "object" | "array";
|
|
9
|
-
|
|
10
|
-
export interface FieldSchema {
|
|
11
|
-
type: FieldType;
|
|
12
|
-
required?: boolean; // default false — missing = NULL not error
|
|
13
|
-
default?: any; // fallback if missing (overrides NULL)
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface StoreSchema {
|
|
17
|
-
name: string; // store identifier (matches site name or custom)
|
|
18
|
-
destination: string; // "./data.json" or "./data.db"
|
|
19
|
-
fields: Record<string, FieldSchema>;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface PiggyStoreConfig {
|
|
23
|
-
stores: StoreSchema[];
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// ── Load piggy.store.json ─────────────────────────────────────────────────────
|
|
27
|
-
|
|
28
|
-
let _config: PiggyStoreConfig | null = null;
|
|
29
|
-
|
|
30
|
-
export function loadStoreConfig(configPath = "./piggy.store.json"): PiggyStoreConfig {
|
|
31
|
-
if (_config) return _config;
|
|
32
|
-
|
|
33
|
-
const abs = resolve(configPath);
|
|
34
|
-
if (!existsSync(abs)) {
|
|
35
|
-
logger.warn(`[store] piggy.store.json not found at ${abs} — store() calls will no-op`);
|
|
36
|
-
return { stores: [] };
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
try {
|
|
40
|
-
_config = JSON.parse(readFileSync(abs, "utf8")) as PiggyStoreConfig;
|
|
41
|
-
logger.success(`[store] loaded ${_config.stores.length} schema(s) from ${abs}`);
|
|
42
|
-
return _config;
|
|
43
|
-
} catch (e: any) {
|
|
44
|
-
logger.error(`[store] failed to parse piggy.store.json: ${e.message}`);
|
|
45
|
-
return { stores: [] };
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function getSchema(storeName: string): StoreSchema | null {
|
|
50
|
-
const config = loadStoreConfig();
|
|
51
|
-
return config.stores.find(s => s.name === storeName) ?? null;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// ── Validate & shape one record against schema ────────────────────────────────
|
|
55
|
-
|
|
56
|
-
export function shapeRecord(data: Record<string, any>, schema: StoreSchema): Record<string, any> {
|
|
57
|
-
const shaped: Record<string, any> = {};
|
|
58
|
-
|
|
59
|
-
for (const [field, def] of Object.entries(schema.fields)) {
|
|
60
|
-
const raw = data[field];
|
|
61
|
-
|
|
62
|
-
// Missing field
|
|
63
|
-
if (raw === undefined || raw === null) {
|
|
64
|
-
if (def.default !== undefined) {
|
|
65
|
-
shaped[field] = def.default;
|
|
66
|
-
} else {
|
|
67
|
-
shaped[field] = null; // NULL — not an error, just absent
|
|
68
|
-
}
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Type coercion / validation
|
|
73
|
-
switch (def.type) {
|
|
74
|
-
case "string":
|
|
75
|
-
shaped[field] = String(raw);
|
|
76
|
-
break;
|
|
77
|
-
case "number":
|
|
78
|
-
const n = Number(raw);
|
|
79
|
-
shaped[field] = isNaN(n) ? null : n;
|
|
80
|
-
break;
|
|
81
|
-
case "boolean":
|
|
82
|
-
shaped[field] = Boolean(raw);
|
|
83
|
-
break;
|
|
84
|
-
case "object":
|
|
85
|
-
shaped[field] = typeof raw === "object" && !Array.isArray(raw) ? raw : null;
|
|
86
|
-
break;
|
|
87
|
-
case "array":
|
|
88
|
-
shaped[field] = Array.isArray(raw) ? raw : null;
|
|
89
|
-
break;
|
|
90
|
-
default:
|
|
91
|
-
shaped[field] = raw;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Extra fields on incoming data are silently dropped — not in schema = ignored
|
|
96
|
-
return shaped;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// ── JSON backend ──────────────────────────────────────────────────────────────
|
|
100
|
-
|
|
101
|
-
function appendToJson(destination: string, record: Record<string, any>): void {
|
|
102
|
-
const abs = resolve(destination);
|
|
103
|
-
mkdirSync(dirname(abs), { recursive: true });
|
|
104
|
-
|
|
105
|
-
let existing: any[] = [];
|
|
106
|
-
if (existsSync(abs)) {
|
|
107
|
-
try {
|
|
108
|
-
existing = JSON.parse(readFileSync(abs, "utf8"));
|
|
109
|
-
if (!Array.isArray(existing)) existing = [existing];
|
|
110
|
-
} catch {
|
|
111
|
-
existing = [];
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
existing.push({ ...record, _storedAt: new Date().toISOString() });
|
|
116
|
-
writeFileSync(abs, JSON.stringify(existing, null, 2), "utf8");
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// ── SQLite backend ────────────────────────────────────────────────────────────
|
|
120
|
-
|
|
121
|
-
function appendToSqlite(destination: string, record: Record<string, any>, schema: StoreSchema): void {
|
|
122
|
-
// Use bun:sqlite if available, else require better-sqlite3
|
|
123
|
-
const abs = resolve(destination);
|
|
124
|
-
mkdirSync(dirname(abs), { recursive: true });
|
|
125
|
-
|
|
126
|
-
const isBun = typeof (globalThis as any).Bun !== "undefined";
|
|
127
|
-
|
|
128
|
-
if (isBun) {
|
|
129
|
-
const { Database } = require("bun:sqlite") as typeof import("bun:sqlite");
|
|
130
|
-
const db = new Database(abs);
|
|
131
|
-
ensureTable(db, schema, "bun");
|
|
132
|
-
insertRecord(db, schema, record, "bun");
|
|
133
|
-
db.close();
|
|
134
|
-
} else {
|
|
135
|
-
const Database = require("better-sqlite3");
|
|
136
|
-
const db = new Database(abs);
|
|
137
|
-
ensureTable(db, schema, "node");
|
|
138
|
-
insertRecord(db, schema, record, "node");
|
|
139
|
-
db.close();
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function ensureTable(db: any, schema: StoreSchema, runtime: "bun" | "node"): void {
|
|
144
|
-
const tableName = schema.name.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
145
|
-
const cols = Object.entries(schema.fields).map(([name, def]) => {
|
|
146
|
-
const sqlType = def.type === "number" ? "REAL"
|
|
147
|
-
: def.type === "boolean" ? "INTEGER"
|
|
148
|
-
: def.type === "object" || def.type === "array" ? "TEXT" // JSON stringified
|
|
149
|
-
: "TEXT";
|
|
150
|
-
return ` ${name} ${sqlType}`;
|
|
151
|
-
});
|
|
152
|
-
cols.push(" _storedAt TEXT");
|
|
153
|
-
|
|
154
|
-
const sql = `CREATE TABLE IF NOT EXISTS ${tableName} (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n${cols.join(",\n")}\n)`;
|
|
155
|
-
|
|
156
|
-
if (runtime === "bun") {
|
|
157
|
-
db.run(sql);
|
|
158
|
-
} else {
|
|
159
|
-
db.prepare(sql).run();
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function insertRecord(db: any, schema: StoreSchema, record: Record<string, any>, runtime: "bun" | "node"): void {
|
|
164
|
-
const tableName = schema.name.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
165
|
-
const fields = [...Object.keys(schema.fields), "_storedAt"];
|
|
166
|
-
const values = fields.map(f => {
|
|
167
|
-
if (f === "_storedAt") return new Date().toISOString();
|
|
168
|
-
const v = record[f];
|
|
169
|
-
// Serialize objects/arrays as JSON strings for SQLite
|
|
170
|
-
if (v !== null && (typeof v === "object" || Array.isArray(v))) return JSON.stringify(v);
|
|
171
|
-
return v ?? null;
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
const placeholders = runtime === "bun"
|
|
175
|
-
? fields.map((_, i) => `?${i + 1}`).join(", ")
|
|
176
|
-
: fields.map(() => "?").join(", ");
|
|
177
|
-
|
|
178
|
-
const sql = `INSERT INTO ${tableName} (${fields.join(", ")}) VALUES (${placeholders})`;
|
|
179
|
-
|
|
180
|
-
if (runtime === "bun") {
|
|
181
|
-
db.run(sql, values);
|
|
182
|
-
} else {
|
|
183
|
-
db.prepare(sql).run(values);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// ── Main store function ───────────────────────────────────────────────────────
|
|
188
|
-
|
|
189
|
-
export async function storeRecord(
|
|
190
|
-
storeName: string,
|
|
191
|
-
data: Record<string, any> | Record<string, any>[]
|
|
192
|
-
): Promise<{ stored: number; skipped: number }> {
|
|
193
|
-
const schema = getSchema(storeName);
|
|
194
|
-
|
|
195
|
-
if (!schema) {
|
|
196
|
-
logger.warn(`[store] no schema found for "${storeName}" in piggy.store.json — data not stored`);
|
|
197
|
-
return { stored: 0, skipped: 1 };
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const records = Array.isArray(data) ? data : [data];
|
|
201
|
-
let stored = 0;
|
|
202
|
-
let skipped = 0;
|
|
203
|
-
|
|
204
|
-
const isJson = schema.destination.endsWith(".json");
|
|
205
|
-
const isSqlite = schema.destination.endsWith(".db") || schema.destination.endsWith(".sqlite");
|
|
206
|
-
|
|
207
|
-
for (const record of records) {
|
|
208
|
-
try {
|
|
209
|
-
const shaped = shapeRecord(record, schema);
|
|
210
|
-
|
|
211
|
-
if (isJson) {
|
|
212
|
-
appendToJson(schema.destination, shaped);
|
|
213
|
-
} else if (isSqlite) {
|
|
214
|
-
appendToSqlite(schema.destination, shaped, schema);
|
|
215
|
-
} else {
|
|
216
|
-
logger.error(`[store] unsupported destination format: ${schema.destination} (use .json or .db)`);
|
|
217
|
-
skipped++;
|
|
218
|
-
continue;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
stored++;
|
|
222
|
-
logger.success(`[store][${storeName}] stored record → ${schema.destination}`);
|
|
223
|
-
} catch (e: any) {
|
|
224
|
-
logger.error(`[store][${storeName}] failed to store record: ${e.message}`);
|
|
225
|
-
skipped++;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
return { stored, skipped };
|
|
1
|
+
// piggy/store/index.ts
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
3
|
+
import { dirname, resolve } from "path";
|
|
4
|
+
import logger from "../logger";
|
|
5
|
+
|
|
6
|
+
// ── Types ─────────────────────────────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
export type FieldType = "string" | "number" | "boolean" | "object" | "array";
|
|
9
|
+
|
|
10
|
+
export interface FieldSchema {
|
|
11
|
+
type: FieldType;
|
|
12
|
+
required?: boolean; // default false — missing = NULL not error
|
|
13
|
+
default?: any; // fallback if missing (overrides NULL)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface StoreSchema {
|
|
17
|
+
name: string; // store identifier (matches site name or custom)
|
|
18
|
+
destination: string; // "./data.json" or "./data.db"
|
|
19
|
+
fields: Record<string, FieldSchema>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface PiggyStoreConfig {
|
|
23
|
+
stores: StoreSchema[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ── Load piggy.store.json ─────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
let _config: PiggyStoreConfig | null = null;
|
|
29
|
+
|
|
30
|
+
export function loadStoreConfig(configPath = "./piggy.store.json"): PiggyStoreConfig {
|
|
31
|
+
if (_config) return _config;
|
|
32
|
+
|
|
33
|
+
const abs = resolve(configPath);
|
|
34
|
+
if (!existsSync(abs)) {
|
|
35
|
+
logger.warn(`[store] piggy.store.json not found at ${abs} — store() calls will no-op`);
|
|
36
|
+
return { stores: [] };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
_config = JSON.parse(readFileSync(abs, "utf8")) as PiggyStoreConfig;
|
|
41
|
+
logger.success(`[store] loaded ${_config.stores.length} schema(s) from ${abs}`);
|
|
42
|
+
return _config;
|
|
43
|
+
} catch (e: any) {
|
|
44
|
+
logger.error(`[store] failed to parse piggy.store.json: ${e.message}`);
|
|
45
|
+
return { stores: [] };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function getSchema(storeName: string): StoreSchema | null {
|
|
50
|
+
const config = loadStoreConfig();
|
|
51
|
+
return config.stores.find(s => s.name === storeName) ?? null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ── Validate & shape one record against schema ────────────────────────────────
|
|
55
|
+
|
|
56
|
+
export function shapeRecord(data: Record<string, any>, schema: StoreSchema): Record<string, any> {
|
|
57
|
+
const shaped: Record<string, any> = {};
|
|
58
|
+
|
|
59
|
+
for (const [field, def] of Object.entries(schema.fields)) {
|
|
60
|
+
const raw = data[field];
|
|
61
|
+
|
|
62
|
+
// Missing field
|
|
63
|
+
if (raw === undefined || raw === null) {
|
|
64
|
+
if (def.default !== undefined) {
|
|
65
|
+
shaped[field] = def.default;
|
|
66
|
+
} else {
|
|
67
|
+
shaped[field] = null; // NULL — not an error, just absent
|
|
68
|
+
}
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Type coercion / validation
|
|
73
|
+
switch (def.type) {
|
|
74
|
+
case "string":
|
|
75
|
+
shaped[field] = String(raw);
|
|
76
|
+
break;
|
|
77
|
+
case "number":
|
|
78
|
+
const n = Number(raw);
|
|
79
|
+
shaped[field] = isNaN(n) ? null : n;
|
|
80
|
+
break;
|
|
81
|
+
case "boolean":
|
|
82
|
+
shaped[field] = Boolean(raw);
|
|
83
|
+
break;
|
|
84
|
+
case "object":
|
|
85
|
+
shaped[field] = typeof raw === "object" && !Array.isArray(raw) ? raw : null;
|
|
86
|
+
break;
|
|
87
|
+
case "array":
|
|
88
|
+
shaped[field] = Array.isArray(raw) ? raw : null;
|
|
89
|
+
break;
|
|
90
|
+
default:
|
|
91
|
+
shaped[field] = raw;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Extra fields on incoming data are silently dropped — not in schema = ignored
|
|
96
|
+
return shaped;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ── JSON backend ──────────────────────────────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
function appendToJson(destination: string, record: Record<string, any>): void {
|
|
102
|
+
const abs = resolve(destination);
|
|
103
|
+
mkdirSync(dirname(abs), { recursive: true });
|
|
104
|
+
|
|
105
|
+
let existing: any[] = [];
|
|
106
|
+
if (existsSync(abs)) {
|
|
107
|
+
try {
|
|
108
|
+
existing = JSON.parse(readFileSync(abs, "utf8"));
|
|
109
|
+
if (!Array.isArray(existing)) existing = [existing];
|
|
110
|
+
} catch {
|
|
111
|
+
existing = [];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
existing.push({ ...record, _storedAt: new Date().toISOString() });
|
|
116
|
+
writeFileSync(abs, JSON.stringify(existing, null, 2), "utf8");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ── SQLite backend ────────────────────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
function appendToSqlite(destination: string, record: Record<string, any>, schema: StoreSchema): void {
|
|
122
|
+
// Use bun:sqlite if available, else require better-sqlite3
|
|
123
|
+
const abs = resolve(destination);
|
|
124
|
+
mkdirSync(dirname(abs), { recursive: true });
|
|
125
|
+
|
|
126
|
+
const isBun = typeof (globalThis as any).Bun !== "undefined";
|
|
127
|
+
|
|
128
|
+
if (isBun) {
|
|
129
|
+
const { Database } = require("bun:sqlite") as typeof import("bun:sqlite");
|
|
130
|
+
const db = new Database(abs);
|
|
131
|
+
ensureTable(db, schema, "bun");
|
|
132
|
+
insertRecord(db, schema, record, "bun");
|
|
133
|
+
db.close();
|
|
134
|
+
} else {
|
|
135
|
+
const Database = require("better-sqlite3");
|
|
136
|
+
const db = new Database(abs);
|
|
137
|
+
ensureTable(db, schema, "node");
|
|
138
|
+
insertRecord(db, schema, record, "node");
|
|
139
|
+
db.close();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function ensureTable(db: any, schema: StoreSchema, runtime: "bun" | "node"): void {
|
|
144
|
+
const tableName = schema.name.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
145
|
+
const cols = Object.entries(schema.fields).map(([name, def]) => {
|
|
146
|
+
const sqlType = def.type === "number" ? "REAL"
|
|
147
|
+
: def.type === "boolean" ? "INTEGER"
|
|
148
|
+
: def.type === "object" || def.type === "array" ? "TEXT" // JSON stringified
|
|
149
|
+
: "TEXT";
|
|
150
|
+
return ` ${name} ${sqlType}`;
|
|
151
|
+
});
|
|
152
|
+
cols.push(" _storedAt TEXT");
|
|
153
|
+
|
|
154
|
+
const sql = `CREATE TABLE IF NOT EXISTS ${tableName} (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n${cols.join(",\n")}\n)`;
|
|
155
|
+
|
|
156
|
+
if (runtime === "bun") {
|
|
157
|
+
db.run(sql);
|
|
158
|
+
} else {
|
|
159
|
+
db.prepare(sql).run();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function insertRecord(db: any, schema: StoreSchema, record: Record<string, any>, runtime: "bun" | "node"): void {
|
|
164
|
+
const tableName = schema.name.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
165
|
+
const fields = [...Object.keys(schema.fields), "_storedAt"];
|
|
166
|
+
const values = fields.map(f => {
|
|
167
|
+
if (f === "_storedAt") return new Date().toISOString();
|
|
168
|
+
const v = record[f];
|
|
169
|
+
// Serialize objects/arrays as JSON strings for SQLite
|
|
170
|
+
if (v !== null && (typeof v === "object" || Array.isArray(v))) return JSON.stringify(v);
|
|
171
|
+
return v ?? null;
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const placeholders = runtime === "bun"
|
|
175
|
+
? fields.map((_, i) => `?${i + 1}`).join(", ")
|
|
176
|
+
: fields.map(() => "?").join(", ");
|
|
177
|
+
|
|
178
|
+
const sql = `INSERT INTO ${tableName} (${fields.join(", ")}) VALUES (${placeholders})`;
|
|
179
|
+
|
|
180
|
+
if (runtime === "bun") {
|
|
181
|
+
db.run(sql, values);
|
|
182
|
+
} else {
|
|
183
|
+
db.prepare(sql).run(values);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ── Main store function ───────────────────────────────────────────────────────
|
|
188
|
+
|
|
189
|
+
export async function storeRecord(
|
|
190
|
+
storeName: string,
|
|
191
|
+
data: Record<string, any> | Record<string, any>[]
|
|
192
|
+
): Promise<{ stored: number; skipped: number }> {
|
|
193
|
+
const schema = getSchema(storeName);
|
|
194
|
+
|
|
195
|
+
if (!schema) {
|
|
196
|
+
logger.warn(`[store] no schema found for "${storeName}" in piggy.store.json — data not stored`);
|
|
197
|
+
return { stored: 0, skipped: 1 };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const records = Array.isArray(data) ? data : [data];
|
|
201
|
+
let stored = 0;
|
|
202
|
+
let skipped = 0;
|
|
203
|
+
|
|
204
|
+
const isJson = schema.destination.endsWith(".json");
|
|
205
|
+
const isSqlite = schema.destination.endsWith(".db") || schema.destination.endsWith(".sqlite");
|
|
206
|
+
|
|
207
|
+
for (const record of records) {
|
|
208
|
+
try {
|
|
209
|
+
const shaped = shapeRecord(record, schema);
|
|
210
|
+
|
|
211
|
+
if (isJson) {
|
|
212
|
+
appendToJson(schema.destination, shaped);
|
|
213
|
+
} else if (isSqlite) {
|
|
214
|
+
appendToSqlite(schema.destination, shaped, schema);
|
|
215
|
+
} else {
|
|
216
|
+
logger.error(`[store] unsupported destination format: ${schema.destination} (use .json or .db)`);
|
|
217
|
+
skipped++;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
stored++;
|
|
222
|
+
logger.success(`[store][${storeName}] stored record → ${schema.destination}`);
|
|
223
|
+
} catch (e: any) {
|
|
224
|
+
logger.error(`[store][${storeName}] failed to store record: ${e.message}`);
|
|
225
|
+
skipped++;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return { stored, skipped };
|
|
230
230
|
}
|