Skip to content

Instantly share code, notes, and snippets.

@jmakeig
Last active January 23, 2026 21:17
Show Gist options
  • Select an option

  • Save jmakeig/9f3571b22165e318ebcf084c62116b1c to your computer and use it in GitHub Desktop.

Select an option

Save jmakeig/9f3571b22165e318ebcf084c62116b1c to your computer and use it in GitHub Desktop.
Updated version of Pending<Entity>.
// types/entities.d.ts
// ============================================================================
// Types (least concrete to most)
// ============================================================================
// Branded ID type using unique symbol
declare const ID_BRAND: unique symbol;
export type ID = string & { readonly [ID_BRAND]: true };
// Nullable wrapper
type Nullable<T> = T | null;
// Check if a type has a property of type ID (i.e., is an entity)
type IsEntity<Entity> = Entity extends Record<string, any>
? { [K in keyof Entity]: Entity[K] extends ID ? true : never }[keyof Entity] extends never
? false
: true
: false;
// Extract the ID property name from an entity
type IDPropertyName<Entity> = {
[K in keyof Entity]: Entity[K] extends ID ? K : never;
}[keyof Entity];
// Create a reference type using the entity's ID property name
type Ref<Entity> = {
[K in IDPropertyName<Entity>]: ID;
};
// Transform a value for pending state
type PendingValue<Value> = Value extends string | number | boolean | Date
? string | Value
: Value extends Array<infer Item>
? IsEntity<Item> extends true
? Array<Ref<Item>>
: Array<Pending<Item>>
: IsEntity<Value> extends true
? Ref<Value>
: Pending<Value>;
// Pending type for form data
export type Pending<Entity> = {
[K in keyof Entity]: K extends IDPropertyName<Entity>
? ID | null
: Nullable<PendingValue<Entity[K]>>;
};
// Exercise entity
export interface Exercise {
exercise: ID;
label: string;
name: string;
description: string | null;
alternatives: Array<Exercise>;
}
// Rest entity
export interface Rest {
rest: ID;
name: string;
description: string | null;
}
// Set value object containing ordered activities
export interface Set {
name: string;
description: string;
activities: Array<{
activity: Exercise | Rest;
duration: number;
guidance: string | null;
}>;
}
// Workout entity containing ordered sets
export interface Workout {
workout: ID;
label: string;
name: string;
description: string;
sets: Array<Set>;
}
// ============================================================================
// Guards
// ============================================================================
// Type guard for activity in set
export function is_activity_in_set(value: unknown): value is {
activity: Exercise | Rest;
duration: number;
guidance: string | null;
} {
return (
typeof value === 'object' &&
value !== null &&
'activity' in value &&
'duration' in value &&
'guidance' in value
);
}
// ============================================================================
// Examples
// ============================================================================
// Positive Exercise example
const valid_exercise: Exercise = {
exercise: 'ex-123' as ID,
label: 'Push-ups',
name: 'Push-ups',
description: 'Standard push-up exercise',
alternatives: []
};
// Negative Exercise example - missing required property
// @ts-expect-error
const invalid_exercise: Exercise = {
exercise: 'ex-456' as ID,
name: 'Pull-ups',
// missing description
alternatives: []
};
// Positive Rest example
const valid_rest: Rest = {
rest: 'rest-001' as ID,
name: 'Short break',
description: 'Brief rest period'
};
// Negative Rest example - missing required property
// @ts-expect-error
const invalid_rest: Rest = {
rest: 'rest-002' as ID,
// missing name
description: null
};
// Positive Pending<Exercise> example
const valid_pending_exercise: Pending<Exercise> = {
exercise: 'ex-789' as ID,
label: 'Squats',
name: 'Squats',
description: null,
alternatives: [{ exercise: 'ex-111' as ID }, { exercise: 'ex-222' as ID }]
};
// Positive Pending<Exercise> example - new instance without ID
const valid_pending_exercise_new: Pending<Exercise> = {
exercise: null,
label: 'Lunges',
name: 'Walking Lunges',
description: 'Forward walking lunges',
alternatives: []
};
// Negative Pending<Exercise> example - using Exercise instead of {exercise: ID}
const invalid_pending_exercise: Pending<Exercise> = {
exercise: 'ex-999' as ID,
label: 'Lunges',
name: 'Lunges',
description: 'Forward lunges',
alternatives: [
{
exercise: 'ex-888' as ID, // should be {exercise: ID}, not full Exercise
// @ts-expect-error
name: 'Squats',
description: null,
alternatives: []
}
]
};
// Positive Set example
const valid_set: Set = {
name: 'Warm-up Routine',
description: 'Basic warm-up exercises',
activities: [
{
activity: {
exercise: 'ex-123' as ID,
label: 'Push-ups',
name: 'Push-ups',
description: 'Standard push-up exercise',
alternatives: []
},
duration: 60,
guidance: 'Keep your core tight and elbows close to body'
},
{
activity: {
rest: 'rest-001' as ID,
name: 'Short break',
description: 'Brief rest period'
},
duration: 30,
guidance: null
}
]
};
// Negative Set example - missing required property
// @ts-expect-error
const invalid_set: Set = {
name: 'Cool-down Routine',
// missing description
activities: []
};
// Positive Pending<Set> example
const valid_pending_set: Pending<Set> = {
name: 'Strength Training',
description: null,
activities: [
{
activity: { exercise: 'ex-123' as ID },
duration: 90,
guidance: 'Focus on form over speed'
},
{
activity: { rest: 'rest-001' as ID },
duration: '30',
guidance: null
}
]
};
// Negative Pending<Set> example - wrong type for activities
const invalid_pending_set: Pending<Set> = {
name: 'Cardio Blast',
description: 'High intensity cardio',
activities: [
{
// @ts-expect-error
name: 'Some set',
description: 'Some description',
activities: []
}
] // should be Pending<activity object>[], not Set[]
};
// Positive Workout example
const valid_workout: Workout = {
workout: 'workout-001' as ID,
label: 'Monday AM',
name: 'Monday Morning Workout',
description: 'Full body strength training routine',
sets: [
{
name: 'Warm-up Routine',
description: 'Basic warm-up exercises',
activities: [
{
activity: {
exercise: 'ex-123' as ID,
label: 'Push-ups',
name: 'Push-ups',
description: 'Standard push-up exercise',
alternatives: []
},
duration: 60,
guidance: 'Keep your core tight and elbows close to body'
}
]
}
]
};
// Negative Workout example - missing required property
// @ts-expect-error
const invalid_workout: Workout = {
workout: 'workout-002' as ID,
name: 'Evening Cardio',
// missing description
sets: []
};
// Positive Pending<Workout> example
const valid_pending_workout: Pending<Workout> = {
workout: 'workout-003' as ID,
label: 'Wed Legs',
name: 'Wednesday Legs',
description: null,
sets: [
{
name: 'Warm-up',
description: 'Light cardio and stretching',
activities: [
{
activity: { exercise: 'ex-123' as ID },
duration: 300,
guidance: null
}
]
},
{
name: 'Main Lifts',
description: null,
activities: []
}
]
};
// Positive Pending<Workout> example - new instance without ID
const valid_pending_workout_new: Pending<Workout> = {
workout: null,
label: 'New Workout',
name: 'Saturday Recovery',
description: 'Light recovery and stretching',
sets: []
};
// Negative Pending<Workout> example - using Set[] instead of Pending<Set>[]
const invalid_pending_workout: Pending<Workout> = {
workout: 'workout-004' as ID,
label: 'Fri Upper',
name: 'Friday Upper Body',
description: 'Chest and back focus',
sets: [
{
name: 'Some set',
description: 'Some description',
activities: [
{
activity: {
exercise: 'ex-456' as ID,
// @ts-expect-error
name: 'Bench Press',
description: null,
alternatives: []
},
duration: 120,
guidance: null
}
]
}
] // should be Pending<Set>[], not Set[]
};
// Counter-example: Circular non-entity (forbidden pattern)
// This would cause infinite recursion if uncommented:
// type Node = {
// value: string;
// next: Node; // circular reference in non-entity
// };
// type PendingNode = Pending<Node>; // Would recurse infinitely
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

Given a type Thing create a type Pending<Thing> with the following behavior:

  • Root-level properties of type ID are nullable
  • Properties that are entities, i.e. that have top-level ID property, are replaced with { id_prop: ID; }, where id_prop is name name of the entity’s ID property.
  • Same for Arrays of entities with ID properties, i.e. Array<{something: ID; …}> is mapped to Array<Ref<Entity>>
  • Primitives are loosened to allow the original type along with string or null, for example for easy instantiation from FormData

Reimplemented previous verision using Claude (Sonnet 4.5)

-- Sample data for workout schema (DML with data clearing)
-- Clear existing data (in reverse dependency order)
DELETE FROM workout.activity;
DELETE FROM workout.set;
DELETE FROM workout.workout;
DELETE FROM workout.exercise;
-- Insert exercises (10 unique exercises)
INSERT INTO workout.exercise (exercise, label, name, description, alternatives) VALUES
('11111111-1111-1111-1111-111111111111', 'standard-push-ups', 'Standard Push-ups', 'Classic push-up exercise for chest and triceps', '{}'),
('22222222-2222-2222-2222-222222222222', 'pull-ups', 'Pull-ups', 'Upper body pulling exercise', '{"11111111-1111-1111-1111-111111111111"}'),
('33333333-3333-3333-3333-333333333333', 'bodyweight-squats', 'Bodyweight Squats', NULL, '{"44444444-4444-4444-4444-444444444444"}'),
('44444444-4444-4444-4444-444444444444', 'walking-lunges', 'Walking Lunges', 'Forward walking lunges', '{"33333333-3333-3333-3333-333333333333"}'),
('55555555-5555-5555-5555-555555555555', 'plank-hold', 'Plank Hold', 'Core stability exercise', '{}'),
('66666666-6666-6666-6666-666666666666', 'burpees', 'Burpees', 'Full body cardio exercise', '{}'),
('77777777-7777-7777-7777-777777777777', 'romanian-deadlifts', 'Romanian Deadlifts', 'Hamstring and lower back exercise', '{}'),
('88888888-8888-8888-8888-888888888888', 'dumbbell-bench-press', 'Dumbbell Bench Press', NULL, '{"11111111-1111-1111-1111-111111111111"}'),
('99999999-9999-9999-9999-999999999999', 'bent-over-rows', 'Bent-over Rows', 'Back exercise for thickness', '{"22222222-2222-2222-2222-222222222222"}'),
('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'overhead-press', 'Overhead Press', 'Shoulder strength exercise', '{}');
-- Insert workouts (3 workouts)
INSERT INTO workout.workout (workout, label, name, description) VALUES
('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 'full-body-strength-training', 'Full Body Strength Training', 'Complete full body workout for overall strength'),
('cccccccc-cccc-cccc-cccc-cccccccccccc', 'upper-body-focus', 'Upper Body Focus', 'Intense upper body session'),
('dddddddd-dddd-dddd-dddd-dddddddddddd', 'leg-day-blast', 'Leg Day Blast', 'Lower body strength and conditioning');
-- Insert sets for Workout 1 (Full Body - 3 sets)
INSERT INTO workout.set (workout, set_order, name, description) VALUES
('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 1, 'Warm-up Circuit', 'Light exercises to warm up'),
('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 2, 'Main Strength', 'Heavy compound movements'),
('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 3, 'Finisher', 'High intensity finisher');
-- Insert sets for Workout 2 (Upper Body - 2 sets)
INSERT INTO workout.set (workout, set_order, name, description) VALUES
('cccccccc-cccc-cccc-cccc-cccccccccccc', 1, 'Push Exercises', 'Chest, shoulders, triceps'),
('cccccccc-cccc-cccc-cccc-cccccccccccc', 2, 'Pull Exercises', 'Back and biceps');
-- Insert sets for Workout 3 (Leg Day - 3 sets)
INSERT INTO workout.set (workout, set_order, name, description) VALUES
('dddddddd-dddd-dddd-dddd-dddddddddddd', 1, 'Quad Focus', 'Quadriceps dominant exercises'),
('dddddddd-dddd-dddd-dddd-dddddddddddd', 2, 'Posterior Chain', 'Hamstrings and glutes'),
('dddddddd-dddd-dddd-dddd-dddddddddddd', 3, 'Core Work', 'Core stability and strength');
-- Activities for Workout 1, Set 1 (Warm-up - 4 activities with 1 rest)
INSERT INTO workout.activity (workout, set_order, activity_order, exercise, duration, guidance) VALUES
('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 1, 1, '66666666-6666-6666-6666-666666666666', 30, 'Keep pace moderate'),
('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 1, 2, NULL, 15, NULL), -- Rest
('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 1, 3, '11111111-1111-1111-1111-111111111111', 45, 'Full range of motion'),
('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 1, 4, '33333333-3333-3333-3333-333333333333', 60, NULL);
-- Activities for Workout 1, Set 2 (Main Strength - 5 activities with 2 rests)
INSERT INTO workout.activity (workout, set_order, activity_order, exercise, duration, guidance) VALUES
('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 2, 1, '77777777-7777-7777-7777-777777777777', 90, 'Keep back neutral'),
('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 2, 2, NULL, 60, 'Deep breathing'), -- Rest
('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 2, 3, '22222222-2222-2222-2222-222222222222', 75, 'Control the negative'),
('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 2, 4, NULL, 60, NULL), -- Rest
('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 2, 5, 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 80, 'Press overhead fully');
-- Activities for Workout 1, Set 3 (Finisher - 3 activities with 1 rest)
INSERT INTO workout.activity (workout, set_order, activity_order, exercise, duration, guidance) VALUES
('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 3, 1, '66666666-6666-6666-6666-666666666666', 120, 'Maximum effort'),
('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 3, 2, NULL, 30, NULL), -- Rest
('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 3, 3, '55555555-5555-5555-5555-555555555555', 60, 'Hold strong plank position');
-- Activities for Workout 2, Set 1 (Push - 5 activities with 2 rests)
INSERT INTO workout.activity (workout, set_order, activity_order, exercise, duration, guidance) VALUES
('cccccccc-cccc-cccc-cccc-cccccccccccc', 1, 1, '88888888-8888-8888-8888-888888888888', 90, NULL),
('cccccccc-cccc-cccc-cccc-cccccccccccc', 1, 2, NULL, 45, NULL), -- Rest
('cccccccc-cccc-cccc-cccc-cccccccccccc', 1, 3, 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 75, 'Strict form'),
('cccccccc-cccc-cccc-cccc-cccccccccccc', 1, 4, NULL, 45, 'Shake out arms'), -- Rest
('cccccccc-cccc-cccc-cccc-cccccccccccc', 1, 5, '11111111-1111-1111-1111-111111111111', 60, 'Push to failure');
-- Activities for Workout 2, Set 2 (Pull - 4 activities with 1 rest)
INSERT INTO workout.activity (workout, set_order, activity_order, exercise, duration, guidance) VALUES
('cccccccc-cccc-cccc-cccc-cccccccccccc', 2, 1, '22222222-2222-2222-2222-222222222222', 80, 'Full hang at bottom'),
('cccccccc-cccc-cccc-cccc-cccccccccccc', 2, 2, NULL, 50, NULL), -- Rest
('cccccccc-cccc-cccc-cccc-cccccccccccc', 2, 3, '99
-- PostgreSQL 17 schema for workout entities (Idempotent DDL)
-- Drop and recreate schema
DROP SCHEMA IF EXISTS workout CASCADE;
CREATE SCHEMA workout;
-- Exercise entity
CREATE TABLE workout.exercise (
exercise UUID PRIMARY KEY DEFAULT gen_random_uuid(),
label TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
description TEXT,
alternatives UUID[] NOT NULL DEFAULT '{}'
);
CREATE INDEX idx_exercise_alternatives ON workout.exercise USING GIN (alternatives);
-- Workout entity
CREATE TABLE workout.workout (
workout UUID PRIMARY KEY DEFAULT gen_random_uuid(),
label TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
description TEXT NOT NULL
);
-- Workout sets (ordered collection, embedded value objects)
CREATE TABLE workout.set (
workout UUID NOT NULL REFERENCES workout.workout(workout) ON DELETE CASCADE,
set_order INTEGER NOT NULL,
name TEXT NOT NULL,
description TEXT NOT NULL,
PRIMARY KEY (workout, set_order)
);
CREATE INDEX idx_set_workout ON workout.set(workout);
-- Set activities (ordered collection)
CREATE TABLE workout.activity (
workout UUID NOT NULL,
set_order INTEGER NOT NULL,
activity_order INTEGER NOT NULL,
exercise UUID REFERENCES workout.exercise(exercise) ON DELETE CASCADE,
duration INTEGER NOT NULL,
guidance TEXT,
PRIMARY KEY (workout, set_order, activity_order),
FOREIGN KEY (workout, set_order) REFERENCES workout.set(workout, set_order) ON DELETE CASCADE
);
CREATE INDEX idx_activity_workout_set ON workout.activity(workout, set_order);
CREATE INDEX idx_activity_exercise ON workout.activity(exercise) WHERE exercise IS NOT NULL;
-- Comments for documentation
COMMENT ON TABLE workout.exercise IS 'Exercise entities with alternatives';
COMMENT ON TABLE workout.workout IS 'Workout entities';
COMMENT ON TABLE workout.set IS 'Ordered sets within workouts (value objects)';
COMMENT ON TABLE workout.activity IS 'Ordered activities within sets (Exercise when exercise is not null, Rest when exercise is null)';
COMMENT ON COLUMN workout.activity.exercise IS 'NULL represents a rest period';
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment