Skip to content
Open
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
f6f2d94
feat: multi-terminology support [wip].
ItzNotABug Sep 28, 2025
3fae9d6
update: restructure dirs.
ItzNotABug Sep 29, 2025
bc16dea
update: abstracted indexes for base views.
ItzNotABug Sep 29, 2025
cd5c781
remove: duplicate components.
ItzNotABug Sep 29, 2025
e451f18
Merge branch 'index-suggestions' into 'multiple-terminologies'.
ItzNotABug Sep 30, 2025
a3eb1c8
fix: import.
ItzNotABug Sep 30, 2025
5eca86e
update: imports and folder structure.
ItzNotABug Sep 30, 2025
aca5241
lint.
ItzNotABug Sep 30, 2025
88a153e
Merge branch 'main' into multiple-terminologies
ItzNotABug Sep 30, 2025
d00e280
misc: changes, docs update, svelte5 migration.
ItzNotABug Sep 30, 2025
7410780
Merge branch 'main' into 'multiple-terminologies'.
ItzNotABug Oct 3, 2025
8f5f4c1
simplify.
ItzNotABug Oct 3, 2025
645ae62
update: migrate empty sheet as a reusable component.
ItzNotABug Oct 3, 2025
6cecb2c
lint.
ItzNotABug Oct 3, 2025
4354c90
update: misc, address a todo.
ItzNotABug Oct 3, 2025
e87894d
update: make breadcrumbs and header reusable.
ItzNotABug Oct 3, 2025
ef7d6d4
update: improved context and terminology sharing.
ItzNotABug Oct 3, 2025
d2c2870
lint.
ItzNotABug Oct 3, 2025
d5afa9b
remove: leftover log
ItzNotABug Oct 5, 2025
466cc8d
address: todo.
ItzNotABug Oct 7, 2025
31aa0ff
address: todo for `trackError`.
ItzNotABug Oct 7, 2025
e1aa598
fixes, stricter params.
ItzNotABug Oct 7, 2025
74e6eae
fixes, stricter params.
ItzNotABug Oct 7, 2025
e06980a
fix: `getContext` undefined issue.
ItzNotABug Oct 7, 2025
95bbe56
update: resizing on indexes.
ItzNotABug Oct 7, 2025
4288215
fix: accessor.
ItzNotABug Oct 7, 2025
a871810
fix: slash issues on routes causing flawed selection on tabs.
ItzNotABug Oct 7, 2025
470a4e1
Merge branch 'main' into 'multiple-terminologies'.
ItzNotABug Oct 7, 2025
7d7dfab
use: `creatingEntity`.
ItzNotABug Oct 7, 2025
fc78c44
remove: legacy logic.
ItzNotABug Oct 7, 2025
6fef9a2
update: proper type.
ItzNotABug Oct 7, 2025
4ef68a4
update: migrate.
ItzNotABug Oct 9, 2025
2ce3811
remove: database derived store.
ItzNotABug Oct 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 16 additions & 10 deletions src/lib/actions/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ const analytics = Analytics({
});

export function trackEvent(name: string, data: object = null): void {
if (!isTrackingAllowed()) {
if (!name || !isTrackingAllowed()) {
return;
}

Expand All @@ -76,7 +76,7 @@ export function trackEvent(name: string, data: object = null): void {
}

export function trackError(exception: Error, event: Submit): void {
if (exception instanceof AppwriteException && exception.type) {
if (exception instanceof AppwriteException && exception.type && event) {
trackEvent(Submit.Error, {
type: exception.type,
form: event
Expand Down Expand Up @@ -148,11 +148,14 @@ export enum Click {
ConnectRepositoryClick = 'click_connect_repository',
CreditsRedeemClick = 'click_credits_redeem',
CloudSignupClick = 'click_cloud_signup',

DatabaseColumnDelete = 'click_column_delete',
DatabaseIndexDelete = 'click_index_delete',
DatabaseTableDelete = 'click_table_delete',
DatabaseRowDelete = 'click_row_delete',
DatabaseDatabaseDelete = 'click_database_delete',
DatabaseImportCsv = 'click_database_import_csv',

DomainCreateClick = 'click_domain_create',
DomainDeleteClick = 'click_domain_delete',
DomainRetryDomainVerificationClick = 'click_domain_retry_domain_verification',
Expand Down Expand Up @@ -265,7 +268,7 @@ export enum Submit {
AuthSessionAlertsUpdate = 'submit_auth_session_alerts_update',
AuthMembershipPrivacyUpdate = 'submit_auth_membership_privacy_update',
AuthMockNumbersUpdate = 'submit_auth_mock_numbers_update',
AuthInvalidateSesssion = 'submit_auth_invalidate_session',
AuthInvalidateSession = 'submit_auth_invalidate_session',
SessionsLengthUpdate = 'submit_sessions_length_update',
SessionsLimitUpdate = 'submit_sessions_limit_update',
SessionDelete = 'submit_session_delete',
Expand All @@ -284,15 +287,18 @@ export enum Submit {
RowDelete = 'submit_row_delete',
RowUpdate = 'submit_row_update',
RowUpdatePermissions = 'submit_row_update_permissions',

IndexCreate = 'submit_index_create',
IndexDelete = 'submit_index_delete',
TableCreate = 'submit_row_create',
TableDelete = 'submit_row_delete',
TableUpdateName = 'submit_row_update_name',
TableUpdatePermissions = 'submit_row_update_permissions',
TableUpdateSecurity = 'submit_row_update_security',
TableUpdateEnabled = 'submit_row_update_enabled',
TableUpdateDisplayNames = 'submit_row_update_display_names',

TableCreate = 'submit_table_create',
TableDelete = 'submit_table_delete',
TableUpdateName = 'submit_table_update_name',
TableUpdatePermissions = 'submit_table_update_permissions',
TableUpdateSecurity = 'submit_table_update_security',
TableUpdateEnabled = 'submit_table_update_enabled',
TableUpdateDisplayNames = 'submit_table_update_display_names',

FunctionCreate = 'submit_function_create',
FunctionDelete = 'submit_function_delete',
FunctionUpdateName = 'submit_function_update_name',
Expand Down
15 changes: 8 additions & 7 deletions src/lib/components/backupRestoreBox.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -57,21 +57,22 @@
}

function updateOrAddItem(payload: Payload) {
// todo: @itznotabug - might need a change to $table?
const { $id, status, $collection, policyId } = payload;
if ($collection === 'archives' && policyId !== null) {
// the internal structure still uses `$collection`,
// and is basically an identifier of the op. type here!
const { $id, status, $collection: type, policyId } = payload;
if (type === 'archives' && policyId !== null) {
return;
}

if ($collection in backupRestoreItems) {
const collectionMap = backupRestoreItems[$collection];
if (type in backupRestoreItems) {
const collectionMap = backupRestoreItems[type];

if (collectionMap.has($id)) {
collectionMap.get($id).status = status;
if (status === 'completed') {
invalidate(Dependencies.BACKUPS);

if ($collection === 'restorations') {
if (type === 'restorations') {
const { newId, newName } =
collectionMap.get($id).options?.['databases']?.['database'][0] || {};

Expand All @@ -81,7 +82,7 @@
} else if (status === 'pending' || status === 'processing' || status === 'uploading') {
collectionMap.set($id, payload);
}
backupRestoreItems[$collection] = collectionMap;
backupRestoreItems[type] = collectionMap;
}
}

Expand Down
37 changes: 37 additions & 0 deletions src/lib/helpers/string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,43 @@ export function singular(str: string): string {
return str.replace(/s$/, '');
}

/**
* Given a string, returns the plural version of it.
*
* Handles common English pluralization rules:
* - Words ending in consonant + y → ies
* - Words ending in vowel + y → s
* - Words ending in sibilants (s, sh, ch, x, z) → es
* - Regular words → s
*
* @export
* @param {string} str
* @returns {string}
*/
export function plural(str: string): string {
if (!str) return str;

const lower = str.toLowerCase();

// Words ending in sibilants: s, sh, ch, x, z
if (/[sxz]$/.test(lower) || /[cs]h$/.test(lower)) {
return str + 'es';
}

// Words ending in consonant + y → ies
// Words ending in vowel + y → s
if (str.endsWith('y')) {
const beforeY = str.slice(-2, -1).toLowerCase();
if (beforeY && !['a', 'e', 'i', 'o', 'u'].includes(beforeY)) {
return str.slice(0, -1) + 'ies';
}
return str + 's';
}

// Default: add 's'
return str + 's';
}

/**
* Convert a dash/underscore/space separated string to camelCase.
*
Expand Down
29 changes: 29 additions & 0 deletions src/lib/stores/navigation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { resolve } from '$app/paths';
import { goto } from '$app/navigation';
import type { Pathname, RouteId, RouteParams } from '$app/types';

// taken directly from svelte's source!
type ResolveArgs<T extends RouteId | Pathname> = T extends RouteId
? RouteParams<T> extends Record<string, never>
? [route: T]
: [route: T, params: RouteParams<T>]
: [route: T];

export function withPath(base: string, ...parts: string[]) {
return [base.replace(/\/+$/, ''), ...parts].join('/');
}

export function resolveRoute<T extends RouteId>(route: T, params?: Record<string, string>) {
// type cast is necessary here!
const resolveArgs = params ? ([route, params] as [T, RouteParams<T>]) : [route];

return resolve(...(resolveArgs as ResolveArgs<T>));
}

export function navigate<T extends RouteId>(
route: T,
params?: Record<string, string>
): Promise<void> {
// type cast is necessary here!
return goto(resolveRoute(route, params));
}
4 changes: 3 additions & 1 deletion src/lib/stores/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ import {
Sites,
Tokens,
TablesDB,
Domains
Domains,
Databases // DocumentsDB later
} from '@appwrite.io/console';
import { Billing } from '../sdk/billing';
import { Backups } from '../sdk/backups';
Expand Down Expand Up @@ -131,6 +132,7 @@ const sdkForProject = {
migrations: new Migrations(clientProject),
sites: new Sites(clientProject),
tablesDB: new TablesDB(clientProject),
documentsDB: new Databases(clientProject), // documentsDB later
console: new Console(clientProject) // for suggestions API
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@
type: 'success',
message: 'Updated session invalidation check.'
});
trackEvent(Submit.AuthInvalidateSesssion);
trackEvent(Submit.AuthInvalidateSession);
} catch (error) {
addNotification({
type: 'error',
message: error.message
});
trackError(error, Submit.AuthInvalidateSesssion);
trackError(error, Submit.AuthInvalidateSession);
}
}
</script>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import type { Page } from '@sveltejs/kit';

import { useTerminology } from './terminology';
import { Submit, Click } from '$lib/actions/analytics';
import type { AnalyticsResult, TerminologyResult, TerminologyShape } from './types';

export function useAnalytics(pageOrTerms: Page | TerminologyResult): AnalyticsResult {
// source is in `TerminologyResult`.
const terminology = 'source' in pageOrTerms ? pageOrTerms : useTerminology(pageOrTerms);

const createSubmitHandler = <TAction extends string>(termType: keyof TerminologyShape) => {
return (action: TAction) => {
const term = terminology.source[termType];
if (!term) {
throw new Error(`No ${termType} terminology found`);
}
const enumKey = `${term.title.singular}${action}`;
return Submit[enumKey as keyof typeof Submit];
};
};

const createClickHandler = <TAction extends string>(termType: keyof TerminologyShape) => {
return (action: TAction) => {
const term = terminology.source[termType];
if (!term) {
throw new Error(`No ${termType} terminology found`);
}
const enumKey = `Database${term.title.singular}${action}`;
return Click[enumKey as keyof typeof Click];
};
};

const result: AnalyticsResult = { submit: {}, click: {} };

if (terminology.entity) {
result.click.entity = createClickHandler('entity');
result.submit.entity = createSubmitHandler('entity');
}

if (terminology.field) {
result.click.field = createClickHandler('field');
result.submit.field = createSubmitHandler('field');
}

if (terminology.record) {
result.click.record = createClickHandler('record');
result.submit.record = createSubmitHandler('record');
}

return result;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { Page } from '@sveltejs/kit';

import { useTerminology } from './terminology';
import { Dependencies } from '$lib/constants';
import type { DependenciesResult, Term, TerminologyResult } from './types';

export function useDependencies(pageOrTerms: Page | TerminologyResult): DependenciesResult {
// source is in `TerminologyResult`.
const terminology = 'source' in pageOrTerms ? pageOrTerms : useTerminology(pageOrTerms);

const getDependencies = (term: { title: Term }) => ({
singular: Dependencies[term.title.singular.toUpperCase() as keyof typeof Dependencies],
plural: Dependencies[term.title.plural.toUpperCase() as keyof typeof Dependencies]
});

return {
entity: terminology.entity ? getDependencies(terminology.entity) : undefined,
field: terminology.field ? getDependencies(terminology.field) : undefined,
record: terminology.record ? getDependencies(terminology.record) : undefined
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from './init';
export * from './types';
export * from './analytics';
export * from './terminology';
export * from './dependencies';
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { Page } from '@sveltejs/kit';
import { getContext, setContext } from 'svelte';
import {
type AnalyticsResult,
type DependenciesResult,
type TerminologyResult,
useAnalytics,
useDependencies,
useTerminology
} from '$database/(entity)';

const TERMINOLOGIES_KEY = Symbol('terminologies');

export type Terminologies = {
analytics: AnalyticsResult;
terminology: TerminologyResult;
dependencies: DependenciesResult;
};

export function getTerminologies(): Terminologies {
return getContext<Terminologies>(TERMINOLOGIES_KEY);
}

export function setTerminologies(page: Page) {
setContext(TERMINOLOGIES_KEY, buildTerminologies(page));
}

function buildTerminologies(page: Page) {
const terminology = useTerminology(page);
return {
terminology,
analytics: useAnalytics(terminology),
dependencies: useDependencies(terminology)
};
}
Loading