@kitledger/core 0.0.2 → 0.0.3

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
+ import { faker } from "@faker-js/faker";
2
+ import { v7 } from "uuid";
3
+ import { BalanceType } from "./accounts.js";
4
+ /**
5
+ * A generic factory function to create an array of items.
6
+ * @param factory A function that creates a single item.
7
+ * @param count The number of items to create.
8
+ * @returns An array of created items.
9
+ */
10
+ export const factory = (factory, count) => {
11
+ return Array.from({ length: count }, factory);
12
+ };
13
+ export class BaseFactory {
14
+ factory;
15
+ constructor(factory) {
16
+ this.factory = factory;
17
+ }
18
+ make(count) {
19
+ return factory(this.factory, count);
20
+ }
21
+ }
22
+ export class ApiTokenFactory extends BaseFactory {
23
+ constructor() {
24
+ super(makeApiToken);
25
+ }
26
+ }
27
+ export class PermissionFactory extends BaseFactory {
28
+ constructor() {
29
+ super(makePermission);
30
+ }
31
+ }
32
+ export class PermissionAssignmentFactory extends BaseFactory {
33
+ constructor() {
34
+ super(makePermissionAssignment);
35
+ }
36
+ }
37
+ export class RoleFactory extends BaseFactory {
38
+ constructor() {
39
+ super(makeRole);
40
+ }
41
+ }
42
+ export class SessionFactory extends BaseFactory {
43
+ constructor() {
44
+ super(makeSession);
45
+ }
46
+ }
47
+ export class SystemPermissionFactory extends BaseFactory {
48
+ constructor() {
49
+ super(makeSystemPermission);
50
+ }
51
+ }
52
+ export class UserFactory extends BaseFactory {
53
+ constructor() {
54
+ super(makeUser);
55
+ }
56
+ }
57
+ export class UserRoleFactory extends BaseFactory {
58
+ constructor() {
59
+ super(makeUserRole);
60
+ }
61
+ }
62
+ const makeApiToken = () => ({
63
+ id: faker.string.uuid(),
64
+ user_id: faker.string.uuid(),
65
+ name: faker.lorem.word(),
66
+ revoked_at: faker.datatype.boolean() ? faker.date.recent() : null,
67
+ });
68
+ const makePermissionAssignment = () => ({
69
+ id: faker.string.uuid(),
70
+ permission_id: faker.string.uuid(),
71
+ user_id: faker.datatype.boolean() ? faker.string.uuid() : null,
72
+ created_at: faker.date.past(),
73
+ updated_at: faker.date.recent(),
74
+ role_id: faker.datatype.boolean() ? faker.string.uuid() : null,
75
+ });
76
+ const makePermission = () => ({
77
+ id: faker.string.uuid(),
78
+ name: `can_${faker.word.verb()}_${faker.word.noun()}`,
79
+ created_at: faker.date.past(),
80
+ updated_at: faker.date.recent(),
81
+ description: faker.lorem.sentence(),
82
+ });
83
+ const makeRole = () => ({
84
+ id: faker.string.uuid(),
85
+ name: faker.person.jobTitle(),
86
+ description: faker.lorem.sentence(),
87
+ created_at: faker.date.past(),
88
+ updated_at: faker.date.recent(),
89
+ });
90
+ const makeSession = () => v7();
91
+ const makeSystemPermission = () => ({
92
+ id: faker.string.uuid(),
93
+ permission: `system:${faker.word.noun()}:${faker.word.verb()}`,
94
+ user_id: faker.string.uuid(),
95
+ created_at: faker.date.past(),
96
+ updated_at: faker.date.recent(),
97
+ });
98
+ const makeUser = () => {
99
+ const firstName = faker.person.firstName();
100
+ const lastName = faker.person.lastName();
101
+ return {
102
+ id: faker.string.uuid(),
103
+ first_name: firstName,
104
+ last_name: lastName,
105
+ email: faker.internet.email({ firstName, lastName }),
106
+ password_hash: faker.internet.password({ length: 60 }),
107
+ created_at: faker.date.past(),
108
+ updated_at: faker.date.recent(),
109
+ };
110
+ };
111
+ const makeUserRole = () => ({
112
+ id: faker.string.uuid(),
113
+ user_id: faker.string.uuid(),
114
+ role_id: faker.string.uuid(),
115
+ created_at: faker.date.past(),
116
+ updated_at: faker.date.recent(),
117
+ });
118
+ export class LedgerFactory extends BaseFactory {
119
+ constructor() {
120
+ super(makeLedger);
121
+ }
122
+ }
123
+ export class AccountFactory extends BaseFactory {
124
+ constructor() {
125
+ super(makeAccount);
126
+ }
127
+ }
128
+ const makeLedger = () => ({
129
+ id: v7(),
130
+ ref_id: v7(),
131
+ alt_id: v7(),
132
+ name: faker.company.name(),
133
+ description: faker.company.catchPhrase(),
134
+ active: true,
135
+ created_at: faker.date.past(),
136
+ updated_at: faker.date.recent(),
137
+ });
138
+ const makeAccount = () => ({
139
+ id: v7(),
140
+ ref_id: v7(),
141
+ alt_id: v7(),
142
+ name: faker.finance.accountName(),
143
+ balance_type: faker.helpers.arrayElement(Object.values(BalanceType)),
144
+ ledger_id: v7(),
145
+ parent_id: v7(),
146
+ active: true,
147
+ created_at: faker.date.past(),
148
+ updated_at: faker.date.recent(),
149
+ meta: {},
150
+ });
@@ -0,0 +1,71 @@
1
+ export declare enum FieldType {
2
+ TEXT = "text",
3
+ NUMBER = "number",
4
+ DATE = "date",
5
+ BOOLEAN = "boolean",
6
+ URL = "url",
7
+ SELECT = "select",
8
+ RELATION = "relation"
9
+ }
10
+ interface BaseField {
11
+ ref_id: string;
12
+ name: string;
13
+ description?: string;
14
+ }
15
+ export interface TextField extends BaseField {
16
+ type: FieldType.TEXT;
17
+ options?: {
18
+ maxLength?: number;
19
+ format?: "email" | "plain" | "rich_text";
20
+ };
21
+ }
22
+ export type NumberFormatting = {
23
+ style: "integer";
24
+ } | {
25
+ style: "decimal";
26
+ precision: number;
27
+ } | {
28
+ style: "currency";
29
+ currencyCode: string;
30
+ };
31
+ export interface NumberField extends BaseField {
32
+ type: FieldType.NUMBER;
33
+ options: {
34
+ min?: number;
35
+ max?: number;
36
+ formatting: NumberFormatting;
37
+ };
38
+ }
39
+ export interface DateField extends BaseField {
40
+ type: FieldType.DATE;
41
+ options?: {
42
+ includeTime: boolean;
43
+ formatStr: string;
44
+ };
45
+ }
46
+ export type QueryConfig = {};
47
+ export interface SelectField extends BaseField {
48
+ type: FieldType.SELECT;
49
+ options: {
50
+ multiSelect: boolean;
51
+ items: Array<{
52
+ label: string;
53
+ value: string | number;
54
+ color?: string;
55
+ }>;
56
+ defaultValue?: string | number | Array<string | number>;
57
+ };
58
+ }
59
+ export interface RelationField extends BaseField {
60
+ type: FieldType.RELATION;
61
+ options: {
62
+ multiSelect: boolean;
63
+ targetEntityId: string;
64
+ displayFieldId: string;
65
+ query: QueryConfig;
66
+ };
67
+ }
68
+ export type Field = TextField | NumberField | DateField | SelectField | RelationField;
69
+ export type FieldOptions = Field;
70
+ export declare function defineField<T extends FieldOptions>(options: T): T;
71
+ export {};
package/dist/fields.js ADDED
@@ -0,0 +1,13 @@
1
+ export var FieldType;
2
+ (function (FieldType) {
3
+ FieldType["TEXT"] = "text";
4
+ FieldType["NUMBER"] = "number";
5
+ FieldType["DATE"] = "date";
6
+ FieldType["BOOLEAN"] = "boolean";
7
+ FieldType["URL"] = "url";
8
+ FieldType["SELECT"] = "select";
9
+ FieldType["RELATION"] = "relation";
10
+ })(FieldType || (FieldType = {}));
11
+ export function defineField(options) {
12
+ return options;
13
+ }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { TransactionModel } from "./transactions.js";
2
1
  import { EntityModel } from "./entities.js";
2
+ import { TransactionModel } from "./transactions.js";
3
3
  export interface KitledgerConfig {
4
4
  transactionModels: TransactionModel[];
5
5
  entityModels: EntityModel[];
package/dist/jwt.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { type JWTPayload } from "jose";
2
+ import { AuthConfigOptions } from "./auth.js";
3
+ export declare enum TokenType {
4
+ SESSION = "SESSION",
5
+ API = "API"
6
+ }
7
+ export declare function verifyToken(authConfig: AuthConfigOptions, token: string): Promise<JWTPayload>;
8
+ export declare function signToken(authConfig: AuthConfigOptions, payload: JWTPayload): Promise<string>;
9
+ export declare function assembleApiTokenJwtPayload(tokenId: string): JWTPayload;
10
+ export declare function assembleSessionJwtPayload(sessionId: string): JWTPayload;
package/dist/jwt.js ADDED
@@ -0,0 +1,66 @@
1
+ import { SignJWT, jwtVerify } from "jose";
2
+ export var TokenType;
3
+ (function (TokenType) {
4
+ TokenType["SESSION"] = "SESSION";
5
+ TokenType["API"] = "API";
6
+ })(TokenType || (TokenType = {}));
7
+ const encodeSecret = (secret) => new TextEncoder().encode(secret);
8
+ export async function verifyToken(authConfig, token) {
9
+ const currentSecret = authConfig.secret;
10
+ try {
11
+ const { payload } = await jwtVerify(token, encodeSecret(currentSecret), {
12
+ algorithms: [authConfig.jwtAlgorithm],
13
+ });
14
+ return payload;
15
+ }
16
+ catch (currentSecretError) {
17
+ const pastSecrets = authConfig.pastSecrets || [];
18
+ if (pastSecrets.length > 0) {
19
+ try {
20
+ const decodedFromPast = await Promise.any(pastSecrets.map(async (pastSecret) => {
21
+ try {
22
+ const { payload } = await jwtVerify(token, encodeSecret(pastSecret), {
23
+ algorithms: [authConfig.jwtAlgorithm],
24
+ });
25
+ return payload;
26
+ }
27
+ catch (err) {
28
+ console.warn(`Failed to verify token with past secrets (${err instanceof Error ? err.message : String(err)}`);
29
+ throw err;
30
+ }
31
+ }));
32
+ return decodedFromPast;
33
+ }
34
+ catch (aggregateError) {
35
+ console.warn("All past secret verifications failed:", aggregateError.errors);
36
+ }
37
+ }
38
+ console.error("Token verification ultimately failed:", currentSecretError);
39
+ throw new Error("Invalid or expired token.");
40
+ }
41
+ }
42
+ export async function signToken(authConfig, payload) {
43
+ const currentSecret = authConfig.secret;
44
+ try {
45
+ const token = await new SignJWT(payload)
46
+ .setProtectedHeader({ alg: authConfig.jwtAlgorithm })
47
+ .sign(encodeSecret(currentSecret));
48
+ return token;
49
+ }
50
+ catch (error) {
51
+ console.error("Failed to sign token:", error);
52
+ throw new Error("Token signing failed.");
53
+ }
54
+ }
55
+ export function assembleApiTokenJwtPayload(tokenId) {
56
+ return {
57
+ jti: tokenId,
58
+ token_type: TokenType.API,
59
+ };
60
+ }
61
+ export function assembleSessionJwtPayload(sessionId) {
62
+ return {
63
+ jti: sessionId,
64
+ token_type: TokenType.SESSION,
65
+ };
66
+ }
@@ -0,0 +1,22 @@
1
+ import { InferInsertModel, InferSelectModel } from "drizzle-orm";
2
+ import * as v from "valibot";
3
+ import { InferOutput } from "valibot";
4
+ import { KitledgerDb } from "./db.js";
5
+ import { FilterOperationParameters, GetOperationResult } from "./db.js";
6
+ import { ledgers } from "./schema.js";
7
+ export declare const LedgerCreateSchema: v.ObjectSchema<{
8
+ readonly ref_id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MaxLengthAction<string, 64, undefined>]>;
9
+ readonly alt_id: v.NullishSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MaxLengthAction<string, 64, undefined>]>, null>;
10
+ readonly name: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.NonEmptyAction<string, undefined>, v.MaxLengthAction<string, 64, undefined>]>;
11
+ readonly description: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MaxLengthAction<string, 255, undefined>]>, undefined>;
12
+ readonly unit_model_id: v.StringSchema<undefined>;
13
+ readonly active: v.NullishSchema<v.BooleanSchema<undefined>, true>;
14
+ readonly created_at: v.OptionalSchema<v.DateSchema<undefined>, undefined>;
15
+ readonly updated_at: v.OptionalSchema<v.NullableSchema<v.DateSchema<undefined>, undefined>, undefined>;
16
+ }, undefined>;
17
+ import { ValidationSuccess, ValidationFailure } from "./validation.js";
18
+ export type LedgerInsert = InferInsertModel<typeof ledgers>;
19
+ export type Ledger = InferSelectModel<typeof ledgers>;
20
+ export type LedgerCreateData = InferOutput<typeof LedgerCreateSchema>;
21
+ export declare function filterLedgers(db: KitledgerDb, params: FilterOperationParameters): Promise<GetOperationResult<Ledger>>;
22
+ export declare function createLedger(db: KitledgerDb, data: LedgerCreateData): Promise<ValidationSuccess<Ledger> | ValidationFailure<LedgerCreateData>>;
@@ -0,0 +1,144 @@
1
+ import { and, eq, sql } from "drizzle-orm";
2
+ import * as v from "valibot";
3
+ import { ANY, defaultLimit, defaultOffset, maxLimit, parseBooleanFilterValue, } from "./db.js";
4
+ import { ledgers } from "./schema.js";
5
+ export const LedgerCreateSchema = v.object({
6
+ ref_id: v.pipe(v.string(), v.maxLength(64)),
7
+ alt_id: v.nullish(v.pipe(v.string(), v.maxLength(64)), null),
8
+ name: v.pipe(v.string(), v.nonEmpty(), v.maxLength(64)),
9
+ description: v.nullable(v.pipe(v.string(), v.maxLength(255))),
10
+ unit_model_id: v.string(),
11
+ active: v.nullish(v.boolean(), true),
12
+ created_at: v.optional(v.date()),
13
+ updated_at: v.optional(v.nullable(v.date())),
14
+ });
15
+ import { v7 } from "uuid";
16
+ import { parseValibotIssues, } from "./validation.js";
17
+ export async function filterLedgers(db, params) {
18
+ const { limit = defaultLimit, offset = defaultOffset, ...filters } = params;
19
+ const filterConditions = [];
20
+ for (const [key, value] of Object.entries(filters)) {
21
+ if (key === ledgers.id.name && String(value).length > 0) {
22
+ filterConditions.push(eq(ledgers.id, String(value)));
23
+ }
24
+ if (key === ledgers.ref_id.name && String(value).length > 0) {
25
+ filterConditions.push(eq(ledgers.ref_id, String(value)));
26
+ }
27
+ if (key === ledgers.alt_id.name && String(value).length > 0) {
28
+ filterConditions.push(eq(ledgers.alt_id, String(value)));
29
+ }
30
+ if (key === ledgers.name.name && String(value).length > 0) {
31
+ filterConditions.push(sql `${ledgers.name} ILIKE ${"%" + String(value) + "%"}`);
32
+ }
33
+ if (key === ledgers.active.name && String(value).length > 0) {
34
+ if (value === ANY) {
35
+ continue;
36
+ }
37
+ const booleanValue = parseBooleanFilterValue(String(value));
38
+ if (booleanValue !== null) {
39
+ filterConditions.push(eq(ledgers.active, booleanValue));
40
+ }
41
+ }
42
+ }
43
+ // By default, only return active ledgers unless explicitly filtered otherwise
44
+ if (!Object.keys(filters).includes(ledgers.active.name)) {
45
+ filterConditions.push(eq(ledgers.active, true));
46
+ }
47
+ const results = await db
48
+ .select()
49
+ .from(ledgers)
50
+ .where(and(...filterConditions))
51
+ .limit(Math.min(limit, maxLimit))
52
+ .offset(offset);
53
+ return {
54
+ data: results,
55
+ limit: Math.min(limit, maxLimit),
56
+ offset: offset,
57
+ count: results.length,
58
+ };
59
+ }
60
+ async function refIdAlreadyExists(db, refId) {
61
+ const results = await db.query.ledgers.findMany({
62
+ where: eq(ledgers.ref_id, refId),
63
+ columns: { id: true },
64
+ });
65
+ return results.length > 0;
66
+ }
67
+ async function altIdAlreadyExists(db, altId) {
68
+ if (!altId) {
69
+ return false;
70
+ }
71
+ const results = await db.query.ledgers.findMany({
72
+ where: eq(ledgers.alt_id, altId),
73
+ columns: { id: true },
74
+ });
75
+ return results.length > 0;
76
+ }
77
+ async function validateLedgerCreate(db, data) {
78
+ const result = v.safeParse(LedgerCreateSchema, data);
79
+ let success = result.success;
80
+ if (!result.success) {
81
+ return {
82
+ success: false,
83
+ errors: parseValibotIssues(result.issues),
84
+ };
85
+ }
86
+ const errors = [];
87
+ const [refIdError, altIdError] = await Promise.all([
88
+ refIdAlreadyExists(db, data.ref_id),
89
+ altIdAlreadyExists(db, data.alt_id ?? null),
90
+ ]);
91
+ if (refIdError) {
92
+ success = false;
93
+ errors.push({
94
+ type: "data",
95
+ path: "ref_id",
96
+ message: "Ref ID already exists.",
97
+ });
98
+ }
99
+ if (altIdError) {
100
+ success = false;
101
+ errors.push({
102
+ type: "data",
103
+ path: "alt_id",
104
+ message: "Alt ID already exists.",
105
+ });
106
+ }
107
+ return {
108
+ success: success,
109
+ data: result.output,
110
+ errors: errors,
111
+ };
112
+ }
113
+ export async function createLedger(db, data) {
114
+ const validation = await validateLedgerCreate(db, data);
115
+ if (!validation.success || !validation.data) {
116
+ return {
117
+ success: false,
118
+ data: data,
119
+ errors: validation.errors,
120
+ };
121
+ }
122
+ const insertData = {
123
+ id: v7(),
124
+ ...validation.data,
125
+ };
126
+ const result = await db.insert(ledgers).values(insertData).returning();
127
+ if (result.length === 0) {
128
+ return {
129
+ success: false,
130
+ data: validation.data,
131
+ errors: [
132
+ {
133
+ type: "data",
134
+ path: null,
135
+ message: "Failed to create ledger.",
136
+ },
137
+ ],
138
+ };
139
+ }
140
+ return {
141
+ success: true,
142
+ data: result[0],
143
+ };
144
+ }
@@ -0,0 +1,19 @@
1
+ import { Query } from "@kitledger/query";
2
+ import { PgTable } from "drizzle-orm/pg-core";
3
+ import { Knex } from "knex";
4
+ import { type KitledgerDb, GetOperationResult, QueryResultRow } from "./db.js";
5
+ /**
6
+ * Prepares and executes a query against the specified table using the provided parameters.
7
+ * @param table
8
+ * @param params
9
+ * @returns
10
+ */
11
+ export declare function executeQuery(db: KitledgerDb, table: PgTable, params: Query): Promise<GetOperationResult<QueryResultRow>>;
12
+ /**
13
+ * Builds a Knex query object from a QueryOptions configuration.
14
+ * @param kx - The Knex instance.
15
+ * @param tableName - The name of the table to query.
16
+ * @param options - The QueryOptions object.
17
+ * @returns A Knex QueryBuilder instance.
18
+ */
19
+ export declare function buildQuery(kx: Knex, tableName: string, options: Query, limit: number, offset: number): Knex.QueryBuilder;