@swoft/gtd-domain
Version:
Getting Things Done (GTD) productivity system - consolidated domain implementation
526 lines (522 loc) • 16.7 kB
JavaScript
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/client/index.ts
var client_exports = {};
__export(client_exports, {
GTDClient: () => GTDClient
});
module.exports = __toCommonJS(client_exports);
// src/contracts/api/backend-response-types.ts
var import_zod = require("zod");
var BackendInboxItemSchema = import_zod.z.object({
id: import_zod.z.string(),
content: import_zod.z.string(),
text: import_zod.z.string().optional(),
title: import_zod.z.string().optional(),
status: import_zod.z.enum([
"unprocessed",
"processed",
"clarified"
]),
capturedAt: import_zod.z.string(),
createdAt: import_zod.z.string(),
capturedBy: import_zod.z.string(),
capturedByPerson: import_zod.z.string().nullable().optional(),
clarification: import_zod.z.string().nullable().optional(),
description: import_zod.z.string().nullable().optional(),
isActionable: import_zod.z.boolean().nullable().optional(),
tags: import_zod.z.array(import_zod.z.string())
});
var BackendProjectSchema = import_zod.z.object({
id: import_zod.z.string(),
name: import_zod.z.string().optional(),
title: import_zod.z.string().optional(),
description: import_zod.z.string(),
status: import_zod.z.enum([
"active",
"completed",
"someday",
"cancelled"
]),
createdAt: import_zod.z.string(),
ownerId: import_zod.z.string().optional(),
createdBy: import_zod.z.string().optional(),
priority: import_zod.z.enum([
"low",
"medium",
"high"
]).optional(),
progress: import_zod.z.object({
totalActions: import_zod.z.number(),
completedActions: import_zod.z.number(),
completionPercentage: import_zod.z.number()
})
});
var BackendSuccessResponseSchema = /* @__PURE__ */ __name((dataSchema) => import_zod.z.object({
success: import_zod.z.literal(true),
data: dataSchema
}), "BackendSuccessResponseSchema");
var BackendErrorResponseSchema = import_zod.z.object({
success: import_zod.z.literal(false),
error: import_zod.z.object({
message: import_zod.z.string(),
code: import_zod.z.string().optional(),
details: import_zod.z.record(import_zod.z.any()).optional()
})
});
var BackendInboxListResponseSchema = import_zod.z.union([
BackendSuccessResponseSchema(import_zod.z.object({
items: import_zod.z.array(BackendInboxItemSchema),
total: import_zod.z.number()
})),
BackendErrorResponseSchema
]);
var BackendProjectsListResponseSchema = import_zod.z.union([
BackendSuccessResponseSchema(import_zod.z.object({
projects: import_zod.z.array(BackendProjectSchema),
total: import_zod.z.number().optional()
})),
BackendErrorResponseSchema
]);
function isBackendSuccessResponse(response) {
return response.success === true;
}
__name(isBackendSuccessResponse, "isBackendSuccessResponse");
var BackendApiError = class _BackendApiError extends Error {
static {
__name(this, "BackendApiError");
}
code;
details;
constructor(code, message, details) {
super(message), this.code = code, this.details = details;
this.name = "BackendApiError";
}
static fromBackendError(errorResponse) {
return new _BackendApiError(errorResponse.error.code || "UNKNOWN_ERROR", errorResponse.error.message, errorResponse.error.details);
}
};
var ApiValidationError = class extends Error {
static {
__name(this, "ApiValidationError");
}
validationErrors;
constructor(message, validationErrors) {
super(message), this.validationErrors = validationErrors;
this.name = "ApiValidationError";
}
};
// src/contracts/api/error-handling.ts
function classifyError(error) {
if (error instanceof TypeError && error.message.includes("fetch")) {
return {
type: "NETWORK_ERROR",
message: "Network connection failed. Please check your internet connection.",
originalError: error
};
}
if (error instanceof BackendApiError) {
return {
type: getErrorTypeFromCode(error.code),
message: error.message,
code: error.code,
details: error.details,
originalError: error
};
}
if (error instanceof ApiValidationError) {
return {
type: "VALIDATION_ERROR",
message: error.message,
details: error.validationErrors,
originalError: error
};
}
if (error.status) {
return classifyHttpError(error.status, error.message || "HTTP Error");
}
return {
type: "UNKNOWN_ERROR",
message: error.message || "An unexpected error occurred",
originalError: error
};
}
__name(classifyError, "classifyError");
function getErrorTypeFromCode(code) {
switch (code) {
case "UNAUTHORIZED":
case "INVALID_TOKEN":
return "AUTHENTICATION_ERROR";
case "FORBIDDEN":
case "INSUFFICIENT_PERMISSIONS":
return "AUTHORIZATION_ERROR";
case "NOT_FOUND":
case "RESOURCE_NOT_FOUND":
return "NOT_FOUND_ERROR";
case "VALIDATION_FAILED":
case "INVALID_REQUEST":
return "VALIDATION_ERROR";
case "SERVER_ERROR":
case "INTERNAL_ERROR":
return "SERVER_ERROR";
case "TIMEOUT":
return "TIMEOUT_ERROR";
default:
return "API_ERROR";
}
}
__name(getErrorTypeFromCode, "getErrorTypeFromCode");
function classifyHttpError(status, message) {
if (status === 401) {
return {
type: "AUTHENTICATION_ERROR",
message: "Authentication required. Please log in.",
code: "UNAUTHORIZED"
};
}
if (status === 403) {
return {
type: "AUTHORIZATION_ERROR",
message: "You do not have permission to perform this action.",
code: "FORBIDDEN"
};
}
if (status === 404) {
return {
type: "NOT_FOUND_ERROR",
message: "The requested resource was not found.",
code: "NOT_FOUND"
};
}
if (status >= 400 && status < 500) {
return {
type: "VALIDATION_ERROR",
message: message || "Invalid request",
code: `HTTP_${status}`
};
}
if (status >= 500) {
return {
type: "SERVER_ERROR",
message: "Server error occurred. Please try again later.",
code: `HTTP_${status}`
};
}
return {
type: "UNKNOWN_ERROR",
message: message || `HTTP error ${status}`,
code: `HTTP_${status}`
};
}
__name(classifyHttpError, "classifyHttpError");
var DEFAULT_RETRY_CONFIG = {
maxRetries: 3,
retryDelay: 1e3,
retryableErrors: [
"NETWORK_ERROR",
"TIMEOUT_ERROR",
"SERVER_ERROR"
]
};
function shouldRetry(error, config = DEFAULT_RETRY_CONFIG) {
return config.retryableErrors.includes(error.type);
}
__name(shouldRetry, "shouldRetry");
async function withRetry(operation, config = DEFAULT_RETRY_CONFIG) {
let lastError;
for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = classifyError(error);
if (attempt === config.maxRetries || !shouldRetry(lastError, config)) {
throw lastError;
}
await new Promise((resolve) => setTimeout(resolve, config.retryDelay * Math.pow(2, attempt)));
}
}
throw lastError;
}
__name(withRetry, "withRetry");
var ConsoleErrorLogger = class {
static {
__name(this, "ConsoleErrorLogger");
}
logError(error, context) {
console.error("API Error:", {
type: error.type,
message: error.message,
code: error.code,
details: error.details,
context,
stack: error.originalError?.stack
});
}
};
var defaultErrorLogger = new ConsoleErrorLogger();
// src/client/gtd-client.ts
var GTDClient = class {
static {
__name(this, "GTDClient");
}
http;
basePath;
config;
constructor(http, basePath = "/api/productivity/gtd", config = {}) {
this.http = http;
this.basePath = basePath;
this.config = {
enableValidation: true,
enableRetry: true,
errorLogger: defaultErrorLogger,
...config
};
}
async executeRequest(operation) {
try {
if (this.config.enableRetry) {
return await withRetry(operation);
}
return await operation();
} catch (error) {
const standardError = classifyError(error);
this.config.errorLogger?.logError(standardError);
throw standardError;
}
}
validateResponse(data, schema, operationName) {
if (!this.config.enableValidation) {
return data;
}
const result = schema.safeParse(data);
if (!result.success) {
throw new ApiValidationError(`Invalid response format for ${operationName}`, result.error.issues);
}
return result.data;
}
// Inbox Operations
async getInboxItems(filters) {
return this.executeRequest(async () => {
const queryParams = filters ? new URLSearchParams(Object.entries(filters).filter(([_, value]) => value !== void 0).reduce((acc, [key, value]) => ({
...acc,
[key]: String(value)
}), {})).toString() : "";
const url = queryParams ? `${this.basePath}/inbox?${queryParams}` : `${this.basePath}/inbox`;
const rawResponse = await this.http.get(url, {
headers: {
"Content-Type": "application/json"
}
});
const validatedResponse = this.validateResponse(rawResponse, BackendInboxListResponseSchema, "getInboxItems");
if (!isBackendSuccessResponse(validatedResponse)) {
throw BackendApiError.fromBackendError(validatedResponse);
}
const { data } = validatedResponse;
return {
items: data.items.map((item) => ({
id: item.id,
originalContent: item.content || item.text || item.title || "",
capturedAt: item.capturedAt,
capturedByPersonId: item.capturedBy || "unknown",
processingStatus: item.status === "processed" ? "processed" : "unprocessed",
clarification: item.clarification || void 0,
isActionable: item.isActionable || void 0,
// These fields are not available in backend response yet - use undefined for now
lastRefinedAt: void 0,
refinedByPersonId: void 0
})),
pagination: {
totalCount: data.total
},
totalCount: data.total
};
});
}
async getInboxItem(id) {
const response = await this.http.get(`${this.basePath}/inbox/${id}`);
return response.item || null;
}
async captureInboxItem(request) {
const response = await this.http.post(`${this.basePath}/inbox`, request);
return response.item || response;
}
async refineInboxItem(id, request) {
const response = await this.http.post(`${this.basePath}/inbox/${id}/refine`, request);
return response.item || response;
}
async clarifyInboxItem(id, request) {
const response = await this.http.post(`${this.basePath}/inbox/${id}/clarify`, request);
return response.item || response;
}
async processInboxItem(id, request) {
return this.http.post(`${this.basePath}/inbox/${id}/process`, request);
}
async deleteInboxItem(id) {
await this.http.delete(`${this.basePath}/inbox/${id}`);
}
// Project Operations
async getProjects(filters) {
const queryParams = filters ? new URLSearchParams(Object.entries(filters).filter(([_, value]) => value !== void 0).reduce((acc, [key, value]) => ({
...acc,
[key]: String(value)
}), {})).toString() : "";
const url = queryParams ? `${this.basePath}/projects?${queryParams}` : `${this.basePath}/projects`;
const response = await this.http.get(url);
if (response.items && response.total !== void 0) {
return {
items: response.items,
pagination: {
totalCount: response.total,
page: response.meta?.page,
pageSize: response.meta?.limit
},
totalCount: response.total
};
}
const items = Array.isArray(response) ? response : [];
return {
items,
pagination: {
totalCount: items.length
},
totalCount: items.length
};
}
async getProject(id) {
const response = await this.http.get(`${this.basePath}/projects/${id}`);
return response.item || null;
}
async createProject(request) {
const response = await this.http.post(`${this.basePath}/projects`, request);
return response.item || response;
}
async completeProject(id, completedBy, completionNotes) {
const response = await this.http.post(`${this.basePath}/projects/${id}/complete`, {
completedBy,
completionNotes
});
return response.item || response;
}
async deferProject(id) {
const response = await this.http.post(`${this.basePath}/projects/${id}/defer`);
return response.item || response;
}
async activateProject(id) {
const response = await this.http.post(`${this.basePath}/projects/${id}/activate`);
return response.item || response;
}
async cancelProject(id) {
const response = await this.http.post(`${this.basePath}/projects/${id}/cancel`);
return response.item || response;
}
async deleteProject(id) {
await this.http.delete(`${this.basePath}/projects/${id}`);
}
// Next Action Operations
async getNextActions(filters) {
const queryParams = filters ? new URLSearchParams(Object.entries(filters).filter(([_, value]) => value !== void 0).reduce((acc, [key, value]) => ({
...acc,
[key]: String(value)
}), {})).toString() : "";
const url = queryParams ? `${this.basePath}/actions?${queryParams}` : `${this.basePath}/actions`;
const response = await this.http.get(url);
if (response.items && response.total !== void 0) {
return {
items: response.items,
pagination: {
totalCount: response.total,
page: response.meta?.page,
pageSize: response.meta?.limit
},
totalCount: response.total
};
}
const items = Array.isArray(response) ? response : [];
return {
items,
pagination: {
totalCount: items.length
},
totalCount: items.length
};
}
async getNextAction(id) {
const response = await this.http.get(`${this.basePath}/actions/${id}`);
return response.item || null;
}
async createNextAction(request) {
const response = await this.http.post(`${this.basePath}/actions`, request);
return response.item || response;
}
async assignNextAction(id, request) {
const response = await this.http.post(`${this.basePath}/actions/${id}/assign`, request);
return response.item || response;
}
async unassignNextAction(id) {
const response = await this.http.post(`${this.basePath}/actions/${id}/unassign`);
return response.item || response;
}
async completeNextAction(id) {
const response = await this.http.post(`${this.basePath}/actions/${id}/complete`);
return response.item || response;
}
async deleteNextAction(id) {
await this.http.delete(`${this.basePath}/actions/${id}`);
}
// Convenience methods that match the existing frontend API
async getActionsByProject(projectId) {
return this.getNextActions({
projectId
});
}
async getActionsByContext(context) {
return this.getNextActions({
context
});
}
async getContexts() {
const response = await this.http.get(`${this.basePath}/contexts`);
const contexts = Array.isArray(response) ? response : [];
return {
items: contexts,
pagination: {
totalCount: contexts.length
},
totalCount: contexts.length
};
}
// Weekly Review
async getWeeklyReview(reviewedBy) {
const queryParams = new URLSearchParams({
reviewedBy
}).toString();
const response = await this.http.get(`${this.basePath}/weekly-review?${queryParams}`, {
headers: {
"Content-Type": "application/json"
}
});
return response.review || response;
}
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
GTDClient
});
//# sourceMappingURL=index.js.map