genjutsu-db 0.1.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/LICENSE +21 -0
- package/README.md +360 -0
- package/dist/client.d.ts +7 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +370 -0
- package/dist/client.js.map +1 -0
- package/dist/errors.d.ts +38 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +59 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/migrations.d.ts +11 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/migrations.js +163 -0
- package/dist/migrations.js.map +1 -0
- package/dist/model.d.ts +27 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +138 -0
- package/dist/model.js.map +1 -0
- package/dist/relations.d.ts +17 -0
- package/dist/relations.d.ts.map +1 -0
- package/dist/relations.js +114 -0
- package/dist/relations.js.map +1 -0
- package/dist/transport.d.ts +32 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +232 -0
- package/dist/transport.js.map +1 -0
- package/dist/types.d.ts +104 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +14 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +57 -0
- package/dist/utils.js.map +1 -0
- package/package.json +48 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migration runner for the genjutsu-db library.
|
|
3
|
+
* Versioned up() migrations with tracking in _genjutsu_migrations sheet.
|
|
4
|
+
*/
|
|
5
|
+
import { getSpreadsheetMetadata, structuralBatchUpdate, getSheetValues, updateSheet, } from "./transport";
|
|
6
|
+
import { schemaError, migrationError } from "./errors";
|
|
7
|
+
const MIGRATIONS_SHEET = "_genjutsu_migrations";
|
|
8
|
+
const MIGRATIONS_RANGE = `${MIGRATIONS_SHEET}!A2:C`;
|
|
9
|
+
/**
|
|
10
|
+
* Run pending migrations, tracking applied ones in _genjutsu_migrations.
|
|
11
|
+
*/
|
|
12
|
+
export async function runMigrations(ctx, migrations) {
|
|
13
|
+
// Validate: no duplicate version numbers
|
|
14
|
+
const versions = new Set();
|
|
15
|
+
for (const m of migrations) {
|
|
16
|
+
if (versions.has(m.version)) {
|
|
17
|
+
throw schemaError(`Migrations have duplicate version number: ${m.version}`);
|
|
18
|
+
}
|
|
19
|
+
versions.add(m.version);
|
|
20
|
+
}
|
|
21
|
+
// Sort migrations by version ascending
|
|
22
|
+
const sorted = [...migrations].sort((a, b) => a.version - b.version);
|
|
23
|
+
// Ensure _genjutsu_migrations sheet exists
|
|
24
|
+
const metadata = await getSpreadsheetMetadata(ctx);
|
|
25
|
+
const sheetIdMap = new Map();
|
|
26
|
+
for (const s of metadata.sheets) {
|
|
27
|
+
sheetIdMap.set(s.title, s.sheetId);
|
|
28
|
+
}
|
|
29
|
+
if (!sheetIdMap.has(MIGRATIONS_SHEET)) {
|
|
30
|
+
await structuralBatchUpdate(ctx, [
|
|
31
|
+
{ addSheet: { properties: { title: MIGRATIONS_SHEET } } },
|
|
32
|
+
]);
|
|
33
|
+
// Refresh metadata after creating the sheet
|
|
34
|
+
const refreshed = await getSpreadsheetMetadata(ctx);
|
|
35
|
+
for (const s of refreshed.sheets) {
|
|
36
|
+
sheetIdMap.set(s.title, s.sheetId);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// Read applied migration versions
|
|
40
|
+
const rows = await getSheetValues(ctx, MIGRATIONS_RANGE, "UNFORMATTED_VALUE");
|
|
41
|
+
const appliedVersions = new Set();
|
|
42
|
+
for (const row of rows) {
|
|
43
|
+
const ver = Number(row[0]);
|
|
44
|
+
if (!Number.isNaN(ver))
|
|
45
|
+
appliedVersions.add(ver);
|
|
46
|
+
}
|
|
47
|
+
// Filter to pending migrations
|
|
48
|
+
const pending = sorted.filter((m) => !appliedVersions.has(m.version));
|
|
49
|
+
if (pending.length === 0)
|
|
50
|
+
return;
|
|
51
|
+
// Create migration context
|
|
52
|
+
const mCtx = createMigrationContext(ctx, sheetIdMap);
|
|
53
|
+
// Execute each pending migration
|
|
54
|
+
for (const m of pending) {
|
|
55
|
+
try {
|
|
56
|
+
await m.up(mCtx);
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
throw migrationError(`Migration ${m.version} (${m.name}) failed: ${err instanceof Error ? err.message : String(err)}`, m.version, m.name, err);
|
|
60
|
+
}
|
|
61
|
+
// Record the successful migration
|
|
62
|
+
const timestamp = new Date().toISOString();
|
|
63
|
+
await updateSheet(ctx, `${MIGRATIONS_SHEET}!A1:C`, [[m.version, m.name, timestamp]], true);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Create a MigrationContext that wraps the transport context.
|
|
68
|
+
*/
|
|
69
|
+
function createMigrationContext(ctx, sheetIdMap) {
|
|
70
|
+
function resolveSheetId(sheetName) {
|
|
71
|
+
const id = sheetIdMap.get(sheetName);
|
|
72
|
+
if (id === undefined) {
|
|
73
|
+
throw schemaError(`Sheet "${sheetName}" not found in spreadsheet`);
|
|
74
|
+
}
|
|
75
|
+
return id;
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
async createSheet(name) {
|
|
79
|
+
await structuralBatchUpdate(ctx, [
|
|
80
|
+
{ addSheet: { properties: { title: name } } },
|
|
81
|
+
]);
|
|
82
|
+
},
|
|
83
|
+
async addColumn(sheet, column, afterIndex) {
|
|
84
|
+
const sheetId = resolveSheetId(sheet);
|
|
85
|
+
const colIndex = afterIndex ?? 0;
|
|
86
|
+
await structuralBatchUpdate(ctx, [
|
|
87
|
+
{
|
|
88
|
+
insertDimension: {
|
|
89
|
+
range: {
|
|
90
|
+
sheetId,
|
|
91
|
+
dimension: "COLUMNS",
|
|
92
|
+
startIndex: colIndex,
|
|
93
|
+
endIndex: colIndex + 1,
|
|
94
|
+
},
|
|
95
|
+
inheritFromBefore: false,
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
updateCells: {
|
|
100
|
+
rows: [
|
|
101
|
+
{
|
|
102
|
+
values: [
|
|
103
|
+
{
|
|
104
|
+
userEnteredValue: { stringValue: column },
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
start: { sheetId, rowIndex: 0, columnIndex: colIndex },
|
|
110
|
+
fields: "userEnteredValue",
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
]);
|
|
114
|
+
},
|
|
115
|
+
async removeColumn(sheet, columnIndex) {
|
|
116
|
+
const sheetId = resolveSheetId(sheet);
|
|
117
|
+
await structuralBatchUpdate(ctx, [
|
|
118
|
+
{
|
|
119
|
+
deleteDimension: {
|
|
120
|
+
range: {
|
|
121
|
+
sheetId,
|
|
122
|
+
dimension: "COLUMNS",
|
|
123
|
+
startIndex: columnIndex,
|
|
124
|
+
endIndex: columnIndex + 1,
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
]);
|
|
129
|
+
},
|
|
130
|
+
async renameColumn(sheet, columnIndex, newName) {
|
|
131
|
+
const sheetId = resolveSheetId(sheet);
|
|
132
|
+
await structuralBatchUpdate(ctx, [
|
|
133
|
+
{
|
|
134
|
+
updateCells: {
|
|
135
|
+
rows: [
|
|
136
|
+
{
|
|
137
|
+
values: [
|
|
138
|
+
{
|
|
139
|
+
userEnteredValue: { stringValue: newName },
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
start: { sheetId, rowIndex: 0, columnIndex },
|
|
145
|
+
fields: "userEnteredValue",
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
]);
|
|
149
|
+
},
|
|
150
|
+
async renameSheet(oldName, newName) {
|
|
151
|
+
const sheetId = resolveSheetId(oldName);
|
|
152
|
+
await structuralBatchUpdate(ctx, [
|
|
153
|
+
{
|
|
154
|
+
updateSheetProperties: {
|
|
155
|
+
properties: { sheetId, title: newName },
|
|
156
|
+
fields: "title",
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
]);
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=migrations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrations.js","sourceRoot":"","sources":["../src/migrations.ts"],"names":[],"mappings":"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"}
|
package/dist/model.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model definition API for the genjutsu-db library.
|
|
3
|
+
* Provides field builders and defineModel() for TypeScript-first schema definition.
|
|
4
|
+
*/
|
|
5
|
+
import type { SheetSchema } from "./types";
|
|
6
|
+
export interface FieldDef<T = unknown> {
|
|
7
|
+
readonly _type: "string" | "number" | "date" | "boolean";
|
|
8
|
+
readonly _isPrimaryKey: boolean;
|
|
9
|
+
readonly _isOptional: boolean;
|
|
10
|
+
readonly _defaultValue: T | undefined;
|
|
11
|
+
readonly _references: {
|
|
12
|
+
model: string;
|
|
13
|
+
field: string;
|
|
14
|
+
} | undefined;
|
|
15
|
+
primaryKey(): FieldDef<T>;
|
|
16
|
+
optional(): FieldDef<T | null>;
|
|
17
|
+
default(value: T): FieldDef<T>;
|
|
18
|
+
references(model: string, field: string): FieldDef<T>;
|
|
19
|
+
}
|
|
20
|
+
export declare const field: {
|
|
21
|
+
string: () => FieldDef<string>;
|
|
22
|
+
number: () => FieldDef<number>;
|
|
23
|
+
date: () => FieldDef<string>;
|
|
24
|
+
boolean: () => FieldDef<boolean>;
|
|
25
|
+
};
|
|
26
|
+
export declare function defineModel<F extends Record<string, FieldDef<any>>>(sheetName: string, fields: F): SheetSchema<any>;
|
|
27
|
+
//# sourceMappingURL=model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAuC,MAAM,SAAS,CAAC;AAOhF,MAAM,WAAW,QAAQ,CAAC,CAAC,GAAG,OAAO;IACnC,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;IACzD,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,aAAa,EAAE,CAAC,GAAG,SAAS,CAAC;IACtC,QAAQ,CAAC,WAAW,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;IACnE,UAAU,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC1B,QAAQ,IAAI,QAAQ,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAC/B,OAAO,CAAC,KAAK,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC/B,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;CACvD;AA8BD,eAAO,MAAM,KAAK;;;;;CAKjB,CAAC;AAWF,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,EACjE,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,CAAC,GACR,WAAW,CAAC,GAAG,CAAC,CAsHlB"}
|
package/dist/model.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model definition API for the genjutsu-db library.
|
|
3
|
+
* Provides field builders and defineModel() for TypeScript-first schema definition.
|
|
4
|
+
*/
|
|
5
|
+
import { schemaError, validationError } from "./errors";
|
|
6
|
+
function createFieldDef(type, isPrimaryKey = false, isOptional = false, defaultValue = undefined, references = undefined) {
|
|
7
|
+
return {
|
|
8
|
+
_type: type,
|
|
9
|
+
_isPrimaryKey: isPrimaryKey,
|
|
10
|
+
_isOptional: isOptional,
|
|
11
|
+
_defaultValue: defaultValue,
|
|
12
|
+
_references: references,
|
|
13
|
+
primaryKey() {
|
|
14
|
+
return createFieldDef(type, true, isOptional, defaultValue, references);
|
|
15
|
+
},
|
|
16
|
+
optional() {
|
|
17
|
+
return createFieldDef(type, isPrimaryKey, true, defaultValue, references);
|
|
18
|
+
},
|
|
19
|
+
default(value) {
|
|
20
|
+
return createFieldDef(type, isPrimaryKey, isOptional, value, references);
|
|
21
|
+
},
|
|
22
|
+
references(model, field) {
|
|
23
|
+
return createFieldDef(type, isPrimaryKey, isOptional, defaultValue, { model, field });
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export const field = {
|
|
28
|
+
string: () => createFieldDef("string"),
|
|
29
|
+
number: () => createFieldDef("number"),
|
|
30
|
+
date: () => createFieldDef("date"),
|
|
31
|
+
boolean: () => createFieldDef("boolean"),
|
|
32
|
+
};
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// defineModel — generates SheetSchema from field declarations
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
function columnLetter(index) {
|
|
37
|
+
// 0-based index to A-Z (only supports up to 26 columns for now)
|
|
38
|
+
return String.fromCharCode(65 + index);
|
|
39
|
+
}
|
|
40
|
+
export function defineModel(sheetName, fields) {
|
|
41
|
+
const entries = Object.entries(fields);
|
|
42
|
+
// Validate
|
|
43
|
+
if (entries.length === 0) {
|
|
44
|
+
throw schemaError("defineModel requires at least one field");
|
|
45
|
+
}
|
|
46
|
+
const primaryKeys = entries.filter(([, f]) => f._isPrimaryKey);
|
|
47
|
+
if (primaryKeys.length === 0) {
|
|
48
|
+
throw schemaError("defineModel requires exactly one field marked as primaryKey");
|
|
49
|
+
}
|
|
50
|
+
if (primaryKeys.length > 1) {
|
|
51
|
+
throw schemaError(`defineModel requires exactly one primaryKey, found ${primaryKeys.length}: ${primaryKeys.map(([n]) => n).join(", ")}`);
|
|
52
|
+
}
|
|
53
|
+
const headers = entries.map(([name]) => name);
|
|
54
|
+
const lastCol = columnLetter(headers.length - 1);
|
|
55
|
+
const primaryKeyName = primaryKeys[0][0];
|
|
56
|
+
// Build FieldDefinition array
|
|
57
|
+
const fieldDefs = entries.map(([name, f]) => ({
|
|
58
|
+
name,
|
|
59
|
+
type: f._type,
|
|
60
|
+
isPrimaryKey: f._isPrimaryKey || undefined,
|
|
61
|
+
isOptional: f._isOptional || undefined,
|
|
62
|
+
defaultValue: f._defaultValue,
|
|
63
|
+
references: f._references,
|
|
64
|
+
}));
|
|
65
|
+
// Build RelationDefinition array
|
|
66
|
+
const relations = entries
|
|
67
|
+
.filter(([, f]) => f._references)
|
|
68
|
+
.map(([name, f]) => ({
|
|
69
|
+
sourceField: name,
|
|
70
|
+
targetModel: f._references.model,
|
|
71
|
+
targetField: f._references.field,
|
|
72
|
+
type: "many-to-one",
|
|
73
|
+
}));
|
|
74
|
+
// Parse a single cell value based on field type
|
|
75
|
+
function parseCell(value, fieldEntry) {
|
|
76
|
+
const [, f] = fieldEntry;
|
|
77
|
+
const isEmpty = value === undefined || value === null || value === "";
|
|
78
|
+
// Use default if available and cell is empty
|
|
79
|
+
if (isEmpty && f._defaultValue !== undefined) {
|
|
80
|
+
return f._defaultValue;
|
|
81
|
+
}
|
|
82
|
+
switch (f._type) {
|
|
83
|
+
case "string":
|
|
84
|
+
case "date":
|
|
85
|
+
return isEmpty ? (f._isOptional ? null : "") : String(value);
|
|
86
|
+
case "number":
|
|
87
|
+
return isEmpty ? (f._isOptional ? null : 0) : Number(value);
|
|
88
|
+
case "boolean":
|
|
89
|
+
if (value === true || value === "TRUE" || value === "true")
|
|
90
|
+
return true;
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const schema = {
|
|
95
|
+
sheetName,
|
|
96
|
+
headers,
|
|
97
|
+
readRange: `${sheetName}!A2:${lastCol}`,
|
|
98
|
+
writeRange: `${sheetName}!A1:${lastCol}`,
|
|
99
|
+
clearRange: `${sheetName}!A2:${lastCol}`,
|
|
100
|
+
primaryKey: primaryKeyName,
|
|
101
|
+
fields: fieldDefs,
|
|
102
|
+
relations: relations.length > 0 ? relations : undefined,
|
|
103
|
+
parseRow(row, _rowIndex) {
|
|
104
|
+
// Skip rows where first cell is empty
|
|
105
|
+
const firstCell = row[0];
|
|
106
|
+
if (firstCell === undefined || firstCell === null || String(firstCell).trim() === "") {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
const entity = {};
|
|
110
|
+
for (let i = 0; i < entries.length; i++) {
|
|
111
|
+
const value = i < row.length ? row[i] : undefined;
|
|
112
|
+
entity[entries[i][0]] = parseCell(value, entries[i]);
|
|
113
|
+
}
|
|
114
|
+
return entity;
|
|
115
|
+
},
|
|
116
|
+
toRow(entity) {
|
|
117
|
+
return headers.map((h) => entity[h]);
|
|
118
|
+
},
|
|
119
|
+
validate(entity) {
|
|
120
|
+
const issues = [];
|
|
121
|
+
for (const [name, f] of entries) {
|
|
122
|
+
const value = entity[name];
|
|
123
|
+
if (!f._isOptional && (value === null || value === undefined)) {
|
|
124
|
+
issues.push({
|
|
125
|
+
field: name,
|
|
126
|
+
message: `Required field "${name}" is missing or null`,
|
|
127
|
+
value,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (issues.length > 0) {
|
|
132
|
+
throw validationError(`Validation failed: ${issues.map((i) => i.message).join(", ")}`, issues);
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
return schema;
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.js","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Relations module for the genjutsu-db library.
|
|
3
|
+
* Provides FK validation on write and eager loading via include.
|
|
4
|
+
*/
|
|
5
|
+
import type { SheetSchema } from "./types";
|
|
6
|
+
import type { TransportContext } from "./transport";
|
|
7
|
+
/**
|
|
8
|
+
* Validate foreign key references for a record before write.
|
|
9
|
+
* Reads the target model's sheet and checks if the referenced record exists.
|
|
10
|
+
*/
|
|
11
|
+
export declare function validateForeignKeys<T>(record: T, schema: SheetSchema<T>, allSchemas: Record<string, SheetSchema<any>>, ctx: TransportContext, changedFields?: Set<string>): Promise<void>;
|
|
12
|
+
/**
|
|
13
|
+
* Load related records for a set of parent records using batchGet.
|
|
14
|
+
* Attaches related records as arrays on each parent by matching FK values.
|
|
15
|
+
*/
|
|
16
|
+
export declare function loadRelated<T>(records: T[], schema: SheetSchema<T>, includeMap: Record<string, true>, allSchemas: Record<string, SheetSchema<any>>, ctx: TransportContext): Promise<T[]>;
|
|
17
|
+
//# sourceMappingURL=relations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relations.d.ts","sourceRoot":"","sources":["../src/relations.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAsB,MAAM,SAAS,CAAC;AAC/D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAIpD;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,CAAC,EACzC,MAAM,EAAE,CAAC,EACT,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,EACtB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,CAAC,CAAC,EAC5C,GAAG,EAAE,gBAAgB,EACrB,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAC1B,OAAO,CAAC,IAAI,CAAC,CAyCf;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAAC,CAAC,EACjC,OAAO,EAAE,CAAC,EAAE,EACZ,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,EACtB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,EAChC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,CAAC,CAAC,EAC5C,GAAG,EAAE,gBAAgB,GACpB,OAAO,CAAC,CAAC,EAAE,CAAC,CAyDd"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Relations module for the genjutsu-db library.
|
|
3
|
+
* Provides FK validation on write and eager loading via include.
|
|
4
|
+
*/
|
|
5
|
+
import { getSheetValues, batchGetValues } from "./transport";
|
|
6
|
+
import { validationError } from "./errors";
|
|
7
|
+
/**
|
|
8
|
+
* Validate foreign key references for a record before write.
|
|
9
|
+
* Reads the target model's sheet and checks if the referenced record exists.
|
|
10
|
+
*/
|
|
11
|
+
export async function validateForeignKeys(record, schema, allSchemas, ctx, changedFields) {
|
|
12
|
+
if (!schema.relations || schema.relations.length === 0)
|
|
13
|
+
return;
|
|
14
|
+
for (const relation of schema.relations) {
|
|
15
|
+
// If changedFields is provided (for update), only validate changed FK fields
|
|
16
|
+
if (changedFields && !changedFields.has(relation.sourceField))
|
|
17
|
+
continue;
|
|
18
|
+
const fkValue = record[relation.sourceField];
|
|
19
|
+
if (fkValue === null || fkValue === undefined)
|
|
20
|
+
continue;
|
|
21
|
+
// Find the target schema by model key
|
|
22
|
+
const targetSchema = allSchemas[relation.targetModel];
|
|
23
|
+
if (!targetSchema)
|
|
24
|
+
continue; // Should have been caught at registration time
|
|
25
|
+
// Read target model's data
|
|
26
|
+
const rows = await getSheetValues(ctx, targetSchema.readRange, "UNFORMATTED_VALUE");
|
|
27
|
+
const targetPk = targetSchema.primaryKey ?? relation.targetField;
|
|
28
|
+
// Find the PK column index
|
|
29
|
+
const pkIndex = targetSchema.headers.indexOf(targetPk);
|
|
30
|
+
if (pkIndex === -1)
|
|
31
|
+
continue;
|
|
32
|
+
// Check if the referenced record exists
|
|
33
|
+
const exists = rows.some((row) => {
|
|
34
|
+
const cellValue = row[pkIndex];
|
|
35
|
+
return cellValue !== undefined && cellValue !== null && String(cellValue) === String(fkValue);
|
|
36
|
+
});
|
|
37
|
+
if (!exists) {
|
|
38
|
+
throw validationError(`Foreign key validation failed: ${relation.sourceField} references ${relation.targetModel}.${relation.targetField} but value "${String(fkValue)}" not found`, [
|
|
39
|
+
{
|
|
40
|
+
field: relation.sourceField,
|
|
41
|
+
message: `Referenced ${relation.targetModel}.${relation.targetField} "${String(fkValue)}" not found`,
|
|
42
|
+
value: fkValue,
|
|
43
|
+
},
|
|
44
|
+
]);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Load related records for a set of parent records using batchGet.
|
|
50
|
+
* Attaches related records as arrays on each parent by matching FK values.
|
|
51
|
+
*/
|
|
52
|
+
export async function loadRelated(records, schema, includeMap, allSchemas, ctx) {
|
|
53
|
+
if (!includeMap || Object.keys(includeMap).length === 0)
|
|
54
|
+
return records;
|
|
55
|
+
// Find all related schemas that should be loaded
|
|
56
|
+
const relatedRanges = [];
|
|
57
|
+
const relatedSchemaKeys = [];
|
|
58
|
+
for (const includeKey of Object.keys(includeMap)) {
|
|
59
|
+
const relatedSchema = allSchemas[includeKey];
|
|
60
|
+
if (!relatedSchema)
|
|
61
|
+
continue;
|
|
62
|
+
relatedRanges.push(relatedSchema.readRange);
|
|
63
|
+
relatedSchemaKeys.push(includeKey);
|
|
64
|
+
}
|
|
65
|
+
if (relatedRanges.length === 0)
|
|
66
|
+
return records;
|
|
67
|
+
// Use batchGet for all related schemas at once
|
|
68
|
+
const batchResult = await batchGetValues(ctx, relatedRanges, "UNFORMATTED_VALUE");
|
|
69
|
+
// For each related schema, find the relation pointing to the current schema
|
|
70
|
+
const pk = schema.primaryKey;
|
|
71
|
+
if (!pk)
|
|
72
|
+
return records;
|
|
73
|
+
// Build a map of parent PK values to their records for fast lookup
|
|
74
|
+
const enhancedRecords = records.map((r) => ({ ...r }));
|
|
75
|
+
for (let i = 0; i < relatedSchemaKeys.length; i++) {
|
|
76
|
+
const relatedKey = relatedSchemaKeys[i];
|
|
77
|
+
const relatedSchema = allSchemas[relatedKey];
|
|
78
|
+
// Find the relation in the related schema that points to the current model
|
|
79
|
+
const relation = findRelationToModel(relatedSchema, allSchemas, schema);
|
|
80
|
+
// Parse the related rows
|
|
81
|
+
const range = relatedRanges[i];
|
|
82
|
+
const rows = batchResult.get(range) ?? [];
|
|
83
|
+
const relatedRecords = [];
|
|
84
|
+
for (let j = 0; j < rows.length; j++) {
|
|
85
|
+
const entity = relatedSchema.parseRow(rows[j], j);
|
|
86
|
+
if (entity != null)
|
|
87
|
+
relatedRecords.push(entity);
|
|
88
|
+
}
|
|
89
|
+
// Attach related records to each parent
|
|
90
|
+
for (const parent of enhancedRecords) {
|
|
91
|
+
const parentPk = parent[pk];
|
|
92
|
+
if (relation) {
|
|
93
|
+
parent[relatedKey] = relatedRecords.filter((child) => String(child[relation.sourceField]) === String(parentPk));
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
parent[relatedKey] = [];
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return enhancedRecords;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Find a relation in the related schema that points back to the current model.
|
|
104
|
+
*/
|
|
105
|
+
function findRelationToModel(relatedSchema, allSchemas, targetSchema) {
|
|
106
|
+
if (!relatedSchema.relations)
|
|
107
|
+
return undefined;
|
|
108
|
+
// Find which key in allSchemas corresponds to targetSchema
|
|
109
|
+
const targetKey = Object.entries(allSchemas).find(([, s]) => s.sheetName === targetSchema.sheetName)?.[0];
|
|
110
|
+
if (!targetKey)
|
|
111
|
+
return undefined;
|
|
112
|
+
return relatedSchema.relations.find((r) => r.targetModel === targetKey);
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=relations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relations.js","sourceRoot":"","sources":["../src/relations.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAE3C;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,MAAS,EACT,MAAsB,EACtB,UAA4C,EAC5C,GAAqB,EACrB,aAA2B;IAE3B,IAAI,CAAC,MAAM,CAAC,SAAS,IAAI,MAAM,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAE/D,KAAK,MAAM,QAAQ,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;QACxC,6EAA6E;QAC7E,IAAI,aAAa,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,QAAQ,CAAC,WAAW,CAAC;YAAE,SAAS;QAExE,MAAM,OAAO,GAAI,MAAkC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;QAC1E,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,KAAK,SAAS;YAAE,SAAS;QAExD,sCAAsC;QACtC,MAAM,YAAY,GAAG,UAAU,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;QACtD,IAAI,CAAC,YAAY;YAAE,SAAS,CAAC,+CAA+C;QAE5E,2BAA2B;QAC3B,MAAM,IAAI,GAAG,MAAM,cAAc,CAAC,GAAG,EAAE,YAAY,CAAC,SAAS,EAAE,mBAAmB,CAAC,CAAC;QACpF,MAAM,QAAQ,GAAG,YAAY,CAAC,UAAU,IAAI,QAAQ,CAAC,WAAW,CAAC;QAEjE,2BAA2B;QAC3B,MAAM,OAAO,GAAG,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACvD,IAAI,OAAO,KAAK,CAAC,CAAC;YAAE,SAAS;QAE7B,wCAAwC;QACxC,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE;YAC/B,MAAM,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC;YAC/B,OAAO,SAAS,KAAK,SAAS,IAAI,SAAS,KAAK,IAAI,IAAI,MAAM,CAAC,SAAS,CAAC,KAAK,MAAM,CAAC,OAAO,CAAC,CAAC;QAChG,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,eAAe,CACnB,kCAAkC,QAAQ,CAAC,WAAW,eAAe,QAAQ,CAAC,WAAW,IAAI,QAAQ,CAAC,WAAW,eAAe,MAAM,CAAC,OAAO,CAAC,aAAa,EAC5J;gBACE;oBACE,KAAK,EAAE,QAAQ,CAAC,WAAW;oBAC3B,OAAO,EAAE,cAAc,QAAQ,CAAC,WAAW,IAAI,QAAQ,CAAC,WAAW,KAAK,MAAM,CAAC,OAAO,CAAC,aAAa;oBACpG,KAAK,EAAE,OAAO;iBACf;aACF,CACF,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,OAAY,EACZ,MAAsB,EACtB,UAAgC,EAChC,UAA4C,EAC5C,GAAqB;IAErB,IAAI,CAAC,UAAU,IAAI,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,OAAO,CAAC;IAExE,iDAAiD;IACjD,MAAM,aAAa,GAAa,EAAE,CAAC;IACnC,MAAM,iBAAiB,GAAa,EAAE,CAAC;IAEvC,KAAK,MAAM,UAAU,IAAI,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;QACjD,MAAM,aAAa,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;QAC7C,IAAI,CAAC,aAAa;YAAE,SAAS;QAC7B,aAAa,CAAC,IAAI,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC;QAC5C,iBAAiB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACrC,CAAC;IAED,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,OAAO,CAAC;IAE/C,+CAA+C;IAC/C,MAAM,WAAW,GAAG,MAAM,cAAc,CAAC,GAAG,EAAE,aAAa,EAAE,mBAAmB,CAAC,CAAC;IAElF,4EAA4E;IAC5E,MAAM,EAAE,GAAG,MAAM,CAAC,UAAU,CAAC;IAC7B,IAAI,CAAC,EAAE;QAAE,OAAO,OAAO,CAAC;IAExB,mEAAmE;IACnE,MAAM,eAAe,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAA8B,CAAA,CAAC,CAAC;IAElF,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,iBAAiB,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAClD,MAAM,UAAU,GAAG,iBAAiB,CAAC,CAAC,CAAC,CAAC;QACxC,MAAM,aAAa,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;QAE7C,2EAA2E;QAC3E,MAAM,QAAQ,GAAG,mBAAmB,CAAC,aAAa,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC;QAExE,yBAAyB;QACzB,MAAM,KAAK,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;QAC/B,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QAC1C,MAAM,cAAc,GAA8B,EAAE,CAAC;QAErD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACrC,MAAM,MAAM,GAAG,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YAClD,IAAI,MAAM,IAAI,IAAI;gBAAE,cAAc,CAAC,IAAI,CAAC,MAAiC,CAAC,CAAC;QAC7E,CAAC;QAED,wCAAwC;QACxC,KAAK,MAAM,MAAM,IAAI,eAAe,EAAE,CAAC;YACrC,MAAM,QAAQ,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC;YAC5B,IAAI,QAAQ,EAAE,CAAC;gBACb,MAAM,CAAC,UAAU,CAAC,GAAG,cAAc,CAAC,MAAM,CACxC,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,KAAK,MAAM,CAAC,QAAQ,CAAC,CACpE,CAAC;YACJ,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC;YAC1B,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,eAAsB,CAAC;AAChC,CAAC;AAED;;GAEG;AACH,SAAS,mBAAmB,CAC1B,aAA+B,EAC/B,UAA4C,EAC5C,YAA8B;IAE9B,IAAI,CAAC,aAAa,CAAC,SAAS;QAAE,OAAO,SAAS,CAAC;IAE/C,2DAA2D;IAC3D,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,IAAI,CAC/C,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,YAAY,CAAC,SAAS,CAClD,EAAE,CAAC,CAAC,CAAC,CAAC;IACP,IAAI,CAAC,SAAS;QAAE,OAAO,SAAS,CAAC;IAEjC,OAAO,aAAa,CAAC,SAAS,CAAC,IAAI,CACjC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,KAAK,SAAS,CACnC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Low-level HTTP transport for Google Sheets v4 REST API.
|
|
3
|
+
* Supports token provider pattern, 401 retry, 403→PERMISSION_ERROR, apiKey for public sheets.
|
|
4
|
+
*/
|
|
5
|
+
export declare const SHEETS_API = "https://sheets.googleapis.com/v4/spreadsheets";
|
|
6
|
+
export interface TransportContext {
|
|
7
|
+
auth?: string | (() => Promise<string>);
|
|
8
|
+
apiKey?: string;
|
|
9
|
+
spreadsheetId: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function extractSpreadsheetId(urlOrId: string): string | null;
|
|
12
|
+
export declare function getSheetValues(ctx: TransportContext, range: string, valueRenderOption?: "FORMATTED_VALUE" | "UNFORMATTED_VALUE"): Promise<unknown[][]>;
|
|
13
|
+
export declare function batchGetValues(ctx: TransportContext, ranges: string[], valueRenderOption?: "FORMATTED_VALUE" | "UNFORMATTED_VALUE"): Promise<Map<string, unknown[][]>>;
|
|
14
|
+
export declare function updateSheet(ctx: TransportContext, range: string, values: unknown[][], append: boolean): Promise<void>;
|
|
15
|
+
export declare function clearRange(ctx: TransportContext, range: string): Promise<void>;
|
|
16
|
+
export declare function batchClear(ctx: TransportContext, ranges: string[]): Promise<void>;
|
|
17
|
+
export declare function batchUpdate(ctx: TransportContext, data: {
|
|
18
|
+
range: string;
|
|
19
|
+
values: unknown[][];
|
|
20
|
+
}[]): Promise<void>;
|
|
21
|
+
export declare function getSpreadsheetMetadata(ctx: TransportContext): Promise<{
|
|
22
|
+
sheets: {
|
|
23
|
+
sheetId: number;
|
|
24
|
+
title: string;
|
|
25
|
+
}[];
|
|
26
|
+
}>;
|
|
27
|
+
export declare function structuralBatchUpdate(ctx: TransportContext, requests: Record<string, unknown>[]): Promise<void>;
|
|
28
|
+
export declare function createSpreadsheet(title: string, auth: string | (() => Promise<string>)): Promise<{
|
|
29
|
+
spreadsheetId: string;
|
|
30
|
+
spreadsheetUrl: string;
|
|
31
|
+
}>;
|
|
32
|
+
//# sourceMappingURL=transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAUH,eAAO,MAAM,UAAU,kDAAkD,CAAC;AAE1E,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,GAAG,CAAC,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IACxC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;CACvB;AAMD,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMnE;AA6GD,wBAAsB,cAAc,CAClC,GAAG,EAAE,gBAAgB,EACrB,KAAK,EAAE,MAAM,EACb,iBAAiB,GAAE,iBAAiB,GAAG,mBAAuC,GAC7E,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAUtB;AAED,wBAAsB,cAAc,CAClC,GAAG,EAAE,gBAAgB,EACrB,MAAM,EAAE,MAAM,EAAE,EAChB,iBAAiB,GAAE,iBAAiB,GAAG,mBAAuC,GAC7E,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC,CAmBnC;AAMD,wBAAsB,WAAW,CAC/B,GAAG,EAAE,gBAAgB,EACrB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,OAAO,EAAE,EAAE,EACnB,MAAM,EAAE,OAAO,GACd,OAAO,CAAC,IAAI,CAAC,CAUf;AAED,wBAAsB,UAAU,CAC9B,GAAG,EAAE,gBAAgB,EACrB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC,CAOf;AAMD,wBAAsB,UAAU,CAC9B,GAAG,EAAE,gBAAgB,EACrB,MAAM,EAAE,MAAM,EAAE,GACf,OAAO,CAAC,IAAI,CAAC,CAaf;AAED,wBAAsB,WAAW,CAC/B,GAAG,EAAE,gBAAgB,EACrB,IAAI,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,EAAE,EAAE,CAAA;CAAE,EAAE,GAC7C,OAAO,CAAC,IAAI,CAAC,CAgBf;AAMD,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,gBAAgB,GACpB,OAAO,CAAC;IAAE,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAAE,CAAC,CAc3D;AAMD,wBAAsB,qBAAqB,CACzC,GAAG,EAAE,gBAAgB,EACrB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAClC,OAAO,CAAC,IAAI,CAAC,CAWf;AAMD,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,GAAG,CAAC,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC,GACrC,OAAO,CAAC;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAAE,CAAC,CAsB5D"}
|