More regEx in VSCode

I had a list of a few thousand words either 3, 4 or 5 letters, i needed to turn the imported list into an array.

const listFourLetterWords = [
ages
foot
good
hope
host
]

Using VSCodes finder with regEx it took moments to turn the 1000’s of words into

const listFourLetterWords = [
"ages",
"foot",
"good",
"hope",
"host",
]

Solution:

Find: \b[a-zA-Z]{4}\b

Replace: “$&”,

Typescript and Mongoose (MongoDb) Discriminators

Many thanks to Manuel Maldonado and his excellent article https://hackernoon.com/how-to-link-mongoose-and-typescript-for-a-single-source-of-truth-94o3uqc. This post carries on from where he left off.

Discriminators – the missing step.

The current project at time of writing makes use of Mongoose’s schema inheritance mechanism. That enables you to have multiple models with overlapping schemas on top of the same underlying MongoDB collection. In other words a single collection that can have multiple models. Mostly this is a cost saving exercise. For more detail see.

https://mongoosejs.com/docs/discriminators.html

https://docs.microsoft.com/en-us/azure/cosmos-db/mongodb-mongoose#using-mongoose-discriminators-to-store-data-in-a-single-collection

How to add types to discriminators

Begin by reading Manuel’s article first!

You will see how to set up models, schemas, statics and methods that are strongly typed and play well with your IDE IntelliSense.

You are using Typescript?

You project is using Typescript and you have installed the following package?

npm i @types/mongoose

https://www.npmjs.com/package/@types/mongoose

Discriminators and Typescript

Here is the finished Base Model:

import { model, Schema } from "mongoose";

const collection = "GeneralPurpose";

const baseOptions = {
  discriminatorKey: "__type",
  collection,
  timestamps: true,
};

export const Base = model("Base", new Schema({}, baseOptions));

Here is the Base Model being used as the inherited model:

import { Document, Model, Types, Schema } from "mongoose";
import { Base } from "./Base.model";

export const MODEL_REF = "JournalBlock";

export interface JournalBlock {
  // _id?: string; // Note, _id is inherited from Document type
  blockTitle: string;
  icon: string;
  createdAt: string;
  updatedAt: string;
  parentId: string;
}

export interface JournalBlockDocument extends JournalBlock, Document {
  minify(): unknown;
}

export interface JournalBlockModel extends Model<JournalBlockDocument> {}

export const JournalBlockSchema: Schema = new Schema({
  blockTitle: { type: String, required: [true, "A title is required"] },
  icon: { type: String, required: [true, "A URL for the icon is required"]},
  introText: {
    type: String,
    required: [true, "A introduction text required"],
    },
  },
});

// Just to prove that hooks are still functioning as expected
JournalBlockSchema.pre("save", function () {
   console.log("PRE SAVE", this);
 }).post("delete", function () {
   console.log("post delete", this);
});

// Add a method. In this case change the returned object
JournalBlockSchema.methods.minify = async function (
  
  this: JournalBlockDocument

) {
  const response: JournalBlock & { _id: string } = {
    _id: this._id,
    icon: this.icon,
    introText: this.introText,
    createdAt: this.createdAt,
    updatedAt: this.updatedAt,
    parentId: this.parentId,
  };
  return response;
};


// This is the magic where we connect Typescript to the Mongoose inherited base model (discriminator)

export const JournalBlockModel = Base.discriminator<
  JournalBlockDocument,
  JournalBlockModel
>(MODEL_REF, JournalBlockSchema);

Use

Import the model into for code as normal. Your IDE will predict the returned Documents from queries like “.findById” including any additional methods or statics

JS Sorting with .localCompare

I’ve been using Javascript for 2 years now and can not remember ever having to sort collections. 95% of what I am doing is backend services work and I try to off load as much data manipulation to the databases, but this week I have had 7 instances of having to sort data.

This caught me out:

For a given collection:

const collection = [ {name: "Dave"}, {name: "Donna"}, {name: "dave"}, {name: "Derek"}, {name: "Dave"},];

Sorted with:

collection.sort(
  (a,b) => { 
    if (a.name < b.name) return -1; 
    if (a.name > b.name) return 1; 
    return 0;
  })

Returns this:

[ { name: 'Dave' },  { name: 'Dave' },  { name: 'Derek' },  { name: 'Donna' },  { name: 'dave' } ]

Which is not what I need.

.localCompare to the rescue

collection.sort(
  (a, b) => a.name.localeCompare(b.name)
)

Returns this:

[ { name: 'dave' },  { name: 'Dave' },  { name: 'Dave' },  { name: 'Derek' },  { name: 'Donna' } ] 

But there is more

.localCompare method has additional options. For the same collection above, this

collection.sort(
  (a, b) => a.name.localeCompare(b.name, 'en', { sensitivity: 'base' })
)

Returns this:

[ { name: 'Dave' },  { name: 'Dave' },  { name: 'dave' },  { name: 'Derek' },  { name: 'Donna' } ] 

Be aware that full implementation of .localCompare in Node.js is dependant on the version you are running.

JSTypescript Debounce

Debounce simply controls how many times a function to be executed over time.

For example, when typing into a search box, you probably don’t want to be calling an external API every time the user presses a key, it is more likely you would wait until they have paused or have finished entering text. Then after second or 2 fetch the data.

If you run the example below f() is called multiple times

let i = 0;
const f = () => {
  console.log(`foo ${++i}`);
};
f(); // foo 1
f(); // foo 2
f(); // foo 3
f(); // foo 4
f(); // fo0 5

console.log(i); // 5

Now wrapping the function inside a debounce method only the last call is completed

const debounce = (
  fnc: (...params: any[]) => unknown,
  n: number,
  immediately: boolean = false
) => {
  let timer: number | undefined = undefined;
  return function (this: any, ...args: any[]) {
    if (timer === undefined && immediately) {
      fnc.apply(this, args);
    }
    clearTimeout(timer);
    timer = setTimeout(() => fnc.apply(this, args), n);
    return timer;
  };
};

let i = 0;
const f = debounce(() => console.log(`foo ${++i}`), 2000);
f(); 
f();
f();
f();
f(); // foo 1

console.log(i) // 0 

To recap; If a user keeps clicking on a button it is only after there has been a pause will the last click be carried out.

Throttling, is very similar to debounce but this time it is the FIRST click that is actioned and subsequent clicks are ignored until a period of time has passed.

Apollo Federation with local schemas

Problem:

We have an eco system of 12+ micro services using either .net or node and each exposing a restful API.

I built my first Apollo GraphQL solution in early 2019 and it works really well, the down side its huge and a pain to maintain as it basically is a collection of types, resolvers and requests to the different services. So we went with federation of the different services.

Now before I begin there is an excellent article here about using local schemas with Apollo Federation and Gateway.

This works but we needed a different approach that allowed us to slowly increment changes and separate out services. We also had the issue that a couple of the services could not be touched (this was a commercial decision).

Solution:

import { ApolloServer } from 'apollo-server-express';
import { ApolloGateway, RemoteGraphQLDataSource, LocalGraphQLDataSource } from '@apollo/gateway';
import { buildFederatedSchema } from '@apollo/federation';
import { DocumentNode } from 'graphql';
import depthLimit from 'graphql-depth-limit';
import userService from './services/user';
import organisationService from './services/organisation';

const remoteServices = {
  Goals: {
    url: 'https://api.mysite.com/goal/graphql',
  },
  Curriculum: {
    url: 'https://api-mysite.com/curriculum/graphql',
  },
};

const localServices = {
  ...userService,
  ...organisationService,
};

const services = ({
  ...localServices,
  ...remoteServices,
} as unknown) as {
  [index: string]: {
    url?: string;
    schema: DocumentNode;
  };
};

const DUMMY_SERVICE_URL = 'localService';

const TOKEN = process.env.KEY || ''; // This string is uses to access the remote services inside or micro service environment. It is used once when the service starts. All other queries and requests us the bearer token supplied by the client
 
    const gateway = new ApolloGateway({
  serviceList: Object.keys(services).map((name) => ({
    name,
    url: services[name].url || DUMMY_SERVICE_URL,
  })),
  __exposeQueryPlanExperimental: false,
  buildService({ name, url }) {
    if (url === DUMMY_SERVICE_URL) {
      return new LocalGraphQLDataSource(buildFederatedSchema(services[name].schema)); // The collection of local services
    } else {
      return new RemoteGraphQLDataSource({
        url,
        willSendRequest({ request, context }) {
          request.http?.headers.set('authorization', context.token || TOKEN); // This is the clients bearer token
        },
      });
    }
  },
});

// Just a regular Apollo Server. Its exported and used in the server/app file
export const gqlServer = new ApolloServer({
  gateway,
  subscriptions: false,
  validationRules: [depthLimit(8)],
  engine: false,
  context: ({ req }) => {
    const authorization = req.headers.authorization || '';
    return { authorization };
  },
  plugins: [
    {
      serverWillStart() {
        console.log('Server starting up!');
      },
    },
  ],
});

What the above allows us to do is create local schemas like this. You can always separate this into different files.

import { gql } from 'apollo-server-express';
import ConfigManager from '../../../lib/configSource/ConfigManager';
import axios from 'axios';
import { IAuthContext, makeHeaders, Error401, NotFound, errorHandling200 } from './utils'; // Common interfaces, a method to create a request header
import errors from '../../../lib/errors';

const BASE_URL = process.env.userProfileAPI;

interface User {
  id: string;
  firstName: string;
  lastName: string;
  displayName: string;
  lastLoggedIn: string;
  roles: string[];
  organizationId: string;
}

const typeDefs = gql`
  type User @key(fields: "id") {
    id: ID!
    email: String
    firstName: String
    lastName: String
    displayName: String
    lastLoggedIn: String
    roles: [String]
    organizationId: String
    organisation: Organisation
  }

  type NotFound {
    message: String
  }

  type Error401 {
    message: String
  }

  union UserResult = User | NotFound | Error401

  extend type Organisation @key(fields: "id") {
    id: ID! @external
    users: [UserResult]
  }

  extend type Query {
    User(id: ID!): UserResult
  }
`;

const resolvers = {
  Query: {
    User: async (
      parent: unknown,
      { id }: { id: string },
      ctx: IAuthContext,
    ): Promise<null | User | NotFound | Error401> => {
      try {
        const { data } = await getUser(id, ctx);
        return {
          __typename: 'User',
          ...data,
        };
      } catch (error) {
        return errorHandling200(error, error.response.status, `User with id "${id}" not found`);
      }
    },
  },
  User: {
    async __resolveReference(user: { id: string }, ctx: IAuthContext): Promise<User | NotFound | Error401> {
      try {
        const { data } = await getUser(user.id, ctx);
        return {
          __typename: 'User',
          ...data,
        };
      } catch (error) {
        return errorHandling200(error, error.response.status, `User with id "${user.id}" not found`);
      }
    },
    async organisation({ organizationId }: { organizationId: string }): Promise<unknown | null> {
      return { __typename: 'Organisation', id: organizationId };
    },
  },
  Organisation: {
    async users(parent: { id: string }, _: unknown, ctx: IAuthContext): Promise<User[] | NotFound | Error401> {
      try {
        const users = await getUserByOrganisation(parent.id, ctx);
        return users.map(user => ({
          __typename: "User",
          ...user
        }));
      } catch (error) {
        return errorHandling200(error, error.response.status, `User with id "${parent.id}" not found`);
      }
    },
  },
};

export default {
  user: {
    schema: {
      typeDefs,
      resolvers,
    },
  },
};

Quick1: JS funky date requirement

The problem is:

Take a date object, set the time to 09:15 the following day. If that day is a Saturday, Sunday or Monday bump the date to the next Tuesday.

Solution:

const reminderTime = (date: Date): Date => {
  const response = new Date(date);
  
  const SUNDAY = 0;
  const MONDAY = 1;
  const TUESDAY = 2;
  const SATURDAY = 6;
  
  response.setDate(response.getDate() + 1);
  
  if ([SUNDAY, MONDAY, SATURDAY].some((x) => x === response.getDay())) {
    response.setDate(response.getDate() + ((TUESDAY + 7 - response.getDay()) % 7));
  }

  response.setHours(9);
  response.setMinutes(15);

  return response;
};

Is the date local or UTC? That depends on how the Date object passed into the method was created. In either case local or UTC is preserved.

Here are some proofs:

console.log(reminderTime(new Date(2020, 10, 2))); 
// Returns Tue Nov 03 2020 09:15:00 GMT+0000 (Greenwich Mean Time) 

console.log(reminderTime(new Date(2020, 10, 3)));
// Returns Wed Nov 04 2020 09:15:00 GMT+0000 (Greenwich Mean Time)

console.log(reminderTime(new Date(2020, 10, 4))); 
// Returns Thu Nov 05 2020 09:15:00 GMT+0000 (Greenwich Mean Time) 

console.log(reminderTime(new Date(2020, 10, 5)));
// Returns Fri Nov 06 2020 09:15:00 GMT+0000 (Greenwich Mean Time) 

console.log(reminderTime(new Date(2020, 10, 6)));
// Returns Tue Nov 10 2020 09:15:00 GMT+0000 (Greenwich Mean Time) 

console.log(reminderTime(new Date(2020, 10, 7)));
// Returns Tue Nov 10 2020 09:15:00 GMT+0000 (Greenwich Mean Time) 

console.log(reminderTime(new Date(2020, 10, 8)));
// Returns Tue Nov 10 2020 09:15:00 GMT+0000 (Greenwich Mean Time) 

console.log(reminderTime(new Date(2020, 10, 9)));
// Returns Tue Nov 10 2020 09:15:00 GMT+0000 (Greenwich Mean Time) 

console.log(reminderTime(new Date(2020, 11, 31)));
// Returns Fri Jan 01 2021 09:15:00 GMT+0000 (Greenwich Mean Time) 

console.log(reminderTime(new Date(2020, 1, 29)));
// Returns Tue Mar 03 2020 09:15:00 GMT+0000 (Greenwich Mean Time) 

console.log(reminderTime(new Date(2020, 1, 30)));
// Returns Tue Mar 03 2020 09:15:00 GMT+0000 (Greenwich Mean Time) 

JS Dates

Dates in JS are difficult and it is easy to reach for an external library like Moment.

But is that true? No, but there are a few gotchas.

There are two “times” we need to concern ourselves with, these are.

  • Local Time and Coordinated Universal Time (UTC). Local time refers to the time zone your computer is operating on.
  • UTC is kinda like Greenwich Mean Time and from what dates and times relate to.

Unless you are doing something really funky the date object uses local time unless you explicitly request UTC. HOWEVER, there is one exception to this. That is creating a date with a string.

Create a date with a string

new Date("2020-11-04")

This is method uses a string to set the date. Pretty straight forward but creating a date with no time is a UTC date/time.

The problem is if your computer is operating in a time zone ahead or behind UTC the date could be out by a day.

SOLUTION 1: Always include hours and minutes as a minimum.

SOLUTION 2: Do not create date objects with strings, use a different method.

Create a date with arguments

new Date(2020, 7, 19, 8, 33)

Creating a date object by using arguments. The above has a value of :

Wed, 19 Aug 2020 08:33 // Local time!

If you look closely it is not really intuitive. August is the 8th month but is represented by the number 7. That is because in Javascript is zero indexed and January is 0 …. December is 11. Not much you can do about it so do not stress and just get on with it. Treat it as a feature, and you wont end up with a UTC time when you wanted a local time.

If you are wondering how to create a UTC date with arguments:

new Date(Date.UTC(2020, 11, 10)); // 10th November 2020 <whatever time it is now>

Create a date without arguments or string

new Date()

Will create a date object with the current local date and time.

Create a date with a timestamp

new Date(1604762586100); // Sat Nov 07 2020 15:23:06 GMT+0000

You can also create a date object using a timestamp. In JavaScript, a timestamp is the amount of milliseconds elapsed since 1 January 1970. I do not remember ever using this method.

Your can get the current timestamp with:

Date.now();

That should be pretty straight forward. The key takeaways are:

  • Know that you are creating a local or UTC date object
  • Maybe avoid creating a date object with a string
  • Remember that months start at 0. January = 0 , December = 11

Quick1: JS Order array of Date objects

Solution: (Typescript)

(range as Date[]).sort((a, b) => a.valueOf() - b.valueOf());

My first thought was why not just:

(range as Date[]).sort()

But that does not work. Have a look at the MDN documents and you will see the comparison is done with character’s Unicode code.

Try this in your browsers console to see the solution working

const range = [
  new Date("2020-09-10"),
  new Date("2020-12-20"),
  new Date("2020-11-12"),
];

range.sort((a, b) => a.valueOf() - b.valueOf())

Quick1: JS array of dates

I always disliked dates in JS, but as time goes by confidence grows. It does not help that I avoid reaching for external packages unless it is really necessary.

Today I needed to generate arrays of numbers that represent days between two dates.

// Outcome needed. Inputs 2020-10-28 and 2020-11-06
const labels = [28,29,30,13,1,2,3,4,5,6];

// Solution
export const getDatesBetweenDates = (
  startDate: string,
  endDate: string
): number[] => {
  const dates: number[] = [];
  const start = new Date(startDate);
  const end = new Date(endDate);
  while (start <= end) {
    dates.push(start.getDate());
    start.setDate(start.getDate() + 1);
  }
  return dates;
};

I have no idea why I thought that would be hard

Quick1: JS, Remove duplicates from an array

There are many ways to do this by looping over the array, then using something like .some() or .findIndex().

How about this as an elegant and simple solution?

[...new Map(users.map((item) => [item.id, item])).values()]

The use case is a collection of aggregated data that could have multiple copies of a user. I needed to remove duplicates:

const users = [
  {id: "1234", name: "kirk"},
  {id: "6789", name: "spock"},
  {id: "1234", name: "kirk"},
  {id: "1234", name: "kirk"},
  {id: "rtyu", name: "bones"},
]

console.log([...new Map(users.map((item) => [item.id, item])).values()])

Copy and paste the code below into your browser console pane to see it work


const users = [
  {id: "1234", name: "kirk"},
  {id: "6789", name: "spock"},
  {id: "1234", name: "kirk"},
  {id: "1234", name: "kirk"},
  {id: "rtyu", name: "bones"},
]

[...new Map(users.map((item) => [item.id, item])).values()]